001
002package gudusoft.gsqlparser.dlineage.dataflow.model;
003
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Map;
007import java.util.Set;
008
009import gudusoft.gsqlparser.EDbVendor;
010import gudusoft.gsqlparser.EExpressionType;
011import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
012import gudusoft.gsqlparser.TSourceToken;
013import gudusoft.gsqlparser.dlineage.util.DlineageUtil;
014import gudusoft.gsqlparser.dlineage.util.Pair3;
015import gudusoft.gsqlparser.nodes.TConstant;
016import gudusoft.gsqlparser.nodes.TExpression;
017import gudusoft.gsqlparser.nodes.TFunctionCall;
018import gudusoft.gsqlparser.nodes.TObjectName;
019import gudusoft.gsqlparser.nodes.TResultColumn;
020import gudusoft.gsqlparser.nodes.TResultColumnList;
021import gudusoft.gsqlparser.sqlenv.TSQLEnv;
022import gudusoft.gsqlparser.util.SQLUtil;
023
024public class TableColumn extends Column{
025
026    @Override
027    public gudusoft.gsqlparser.sqlenv.CanonKey getIdentifierKey() {
028        // Variables, cursors and system pseudo-columns are not catalog columns.
029        return table == null || table.isVariable() || table.isCursor() || table.isConstant() || isPseduo
030                ? null : super.getIdentifierKey();
031    }
032
033
034    private Table table;
035
036    private long id;
037    protected String name;
038
039    protected Pair3<Long, Long, String> startPosition;
040    protected Pair3<Long, Long, String> endPosition;
041
042    private TObjectName columnObject;
043    
044    private boolean showStar;
045    
046    private boolean expandStar = true;
047    
048        private Integer columnIndex;
049        
050        private boolean isVariant;
051        
052        private boolean isPseduo = false;
053        
054        private boolean notBindStarColumn = false;
055        
056        private String displayName;
057
058    private String dataType;
059
060    private Boolean primaryKey = false;
061
062    private Boolean unqiueKey = false;
063
064    private Boolean indexKey = false;
065
066    private Boolean foreignKey = false;
067    
068    private boolean isStruct = false;
069
070        private List<Transform> transforms;
071        
072    private List<Object> candidateParents;
073
074    private TableColumn sourceColumn;
075
076    /** For a field path node, the node it is a field or an array element of: its declared column or a field. */
077    private TableColumn parentColumn;
078
079    /** For a field path node, its path under the declared column, such as {@code customer.address.city}. */
080    private gudusoft.gsqlparser.common.structured.StructuredColumnPath fieldPath;
081
082    /**
083     * The kind of type the DDL declares this column or field with (STRUCT, ARRAY, MAP, scalar); null when no DDL
084     * declares it. A subscript read of a MAP reads its value node, of an ARRAY its element node.
085     */
086    private gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind declaredKind;
087
088        public TableColumn(Table view, TObjectName columnObject, int index) {
089                this(view, columnObject);
090                this.columnIndex = index;
091        }
092
093        /**
094         * A field path node: a field or an array element of a STRUCT or ARRAY column, a child of {@code parent} in the
095         * column's tree (the declared column, or the field above). Its identity is structural, the declared column and
096         * the path segments, so it carries no name key; its name is the path as v1 output writes it,
097         * {@code customer.address.city} or {@code items[*].item_name}, for display only.
098         *
099         * @param table        the table of the declared column
100         * @param columnObject the name whose position the node reports: the field's definition, or the read that made it
101         * @param parent       the node this one is a field or an array element of
102         * @param fieldPath    the path under the declared column
103         */
104        TableColumn(Table table, TObjectName columnObject, TableColumn parent,
105                        gudusoft.gsqlparser.common.structured.StructuredColumnPath fieldPath) {
106                this(table, columnObject);
107                this.parentColumn = parent;
108                this.fieldPath = fieldPath;
109                this.name = fieldPath.toDisplayString();
110                this.isStruct = true;
111                setIdentifierKey(null);
112                parent.children.add(this);
113                table.addFieldPathColumn(this);
114        }
115
116        /** The field path nodes directly below this column or field, in the order they were made. */
117        private final List<TableColumn> children = new ArrayList<TableColumn>(0);
118
119    public TableColumn(Table table, TObjectName columnObject) {
120        if (table == null || columnObject == null)
121            throw new IllegalArgumentException("TableColumn arguments can't be null.");
122
123        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
124
125        this.columnObject = columnObject;
126        setIdentifierKey(DlineageUtil.columnKey(columnObject));
127        setStarColumn(DlineageUtil.isStarName(columnObject));
128
129        TSourceToken startToken = columnObject.getStartToken();
130        TSourceToken endToken = columnObject.getEndToken();
131        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
132                startToken.columnNo, ModelBindingManager.getGlobalHash());
133        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
134                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
135
136        if (columnObject.getColumnNameOnly() != null
137                && !"".equals(columnObject.getColumnNameOnly())) {
138                        if (columnObject.getPartToken() != null && columnObject.getPropertyToken() != null) {
139                                if(supportProperty() && columnObject.getSourceTable() == table.getTableObject()) {
140                                        // Use resolver info to build struct name without table alias prefix
141                                        String structName = null;
142                                        gudusoft.gsqlparser.resolver2.model.ColumnSource cs = columnObject.getColumnSource();
143                                        if (cs != null && cs.isStructFieldAccess() && cs.hasFieldPath()) {
144                                                structName = cs.getFieldPath().toFullReference(cs.getExposedName());
145                                        }
146                                        if (structName == null) {
147                                                gudusoft.gsqlparser.resolver2.model.StructFieldHint hint = columnObject.getStructFieldHint();
148                                                if (hint != null && hint.getFieldPath() != null) {
149                                                        structName = hint.toFullReference();
150                                                }
151                                        }
152                                        if (structName != null) {
153                                                this.name = structName;
154                                        } else {
155                                                // Non-struct qualified name: keep original toString() behavior
156                                                this.name = columnObject.toString();
157                                        }
158                                }
159                                else if (gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor()).readsFieldChains()) {
160                                        // Trino family: payload.address.city reads the column payload (Mantis 4811, 4822). An INSERT or
161                                        // CTAS column named after such a read takes its last field instead (ModelFactory.targetFieldLeaf).
162                                        this.name = DlineageUtil.decodeColumnIdentifier(columnObject.getColumnNameOnly());
163                                }
164                                else {
165                                        this.name = DlineageUtil.propertyColumnPath(columnObject);
166                                }
167                        }
168                        else if(columnObject.getColumnSource()!=null && columnObject.getColumnSource().isStructFieldAccess()) {
169                                if(supportProperty() && columnObject.getSourceTable() == table.getTableObject()) {
170                                        // Use resolver info to build struct name without table alias prefix
171                                        String structName = null;
172                                        gudusoft.gsqlparser.resolver2.model.ColumnSource cs = columnObject.getColumnSource();
173                                        if (cs != null && cs.hasFieldPath()) {
174                                                structName = cs.getFieldPath().toFullReference(cs.getExposedName());
175                                        }
176                                        if (structName != null) {
177                                                this.name = structName;
178                                        } else {
179                                                this.name = columnObject.toString();
180                                        }
181                                } else {
182                                        this.name = DlineageUtil.decodeColumnIdentifier(columnObject.getColumnNameOnly());
183                                }
184                        }
185                        else if(columnObject.getStructFieldHint() != null && supportProperty()
186                                        && !isQualifiedColumnReference(columnObject)) {
187                                this.name = columnObject.getStructFieldHint().toFullReference();
188                        }
189                        else {
190                                this.name = DlineageUtil.decodeColumnIdentifier(columnObject.getColumnNameOnly());
191                        }
192        }
193        else if (columnObject.toString().indexOf(".") != -1 && "".equals(columnObject.getColumnNameOnly()) &&
194                        supportProperty()) {
195                this.name = columnObject.toString();
196                }
197                else if (columnObject.toString().indexOf(".") != -1) {
198                        List<String> splits = SQLUtil.parseNames(columnObject.toString());
199                        this.name = DlineageUtil.decodeColumnIdentifier(splits.get(splits.size() - 1));
200                }
201        else {
202            // A SELECT alias reaches here with a null part token
203            // (dbObjectType=column_alias, so getColumnNameOnly() is empty), which
204            // is why a CREATE VIEW published '[Volgorde]' / 'Volgorde' verbatim
205            // as the view's column name (GitHub issue #704). The argument is a
206            // TObjectName — an identifier by construction, never a value
207            // literal — so decoding its delimiters here is sound.
208            this.name = DlineageUtil.decodeAliasIdentifier(columnObject.toString());
209        }
210
211        this.table = table;
212        table.addColumn(this);
213        
214        if(isStarColumn() && !table.isDetermined()){
215                showStar = true;
216        }
217    }
218
219        /** @deprecated read {@code NestedAccessProfile.publishesPropertyNames()} of the analysis's vendor. */
220        @Deprecated
221        protected boolean supportProperty() {
222                return gudusoft.gsqlparser.nodes.structured.NestedAccessProfile.forVendor(ModelBindingManager.getGlobalVendor())
223                                .publishesPropertyNames();
224        }
225
226
227        /**
228         * Check if a column reference is a standard qualified table.column reference
229         * (not a struct field access). Detects when tableToken matches the source table's name,
230         * e.g., "catalog.Hospital.table_update_01.Position" where table_update_01 is the table.
231         */
232        private boolean isQualifiedColumnReference(TObjectName columnObject) {
233                if (columnObject.getSourceTable() == null || columnObject.getTableToken() == null) {
234                        return false;
235                }
236                String tableTokenStr = columnObject.getTableToken().getAstext();
237                gudusoft.gsqlparser.nodes.TTable src = columnObject.getSourceTable();
238                // Compare tableToken with the source table's simple name (last segment)
239                String tableName = src.getTableName() != null ? src.getTableName().getTableString() : "";
240                List<String> parts = SQLUtil.parseNames(tableName);
241                String simpleTableName = parts.isEmpty() ? tableName : parts.get(parts.size() - 1);
242                if (sameTableName(tableTokenStr, simpleTableName)) {
243                        return true;
244                }
245                // Also check alias
246                String aliasName = src.getAliasName();
247                if (aliasName != null && sameTableName(tableTokenStr, aliasName)) {
248                        return true;
249                }
250                return false;
251        }
252
253        /**
254         * Table-name/alias equality routed through the unified façade; falls back to
255         * case-insensitive comparison when no analysis vendor is bound.
256         */
257        private static boolean sameTableName(String a, String b) {
258                if (a == null || b == null) {
259                        return false;
260                }
261                if (DlineageUtil.configuresIdentifierRules()) {
262                        return DlineageUtil.sameName(ESQLDataObjectType.dotTable, a, b);
263                }
264                EDbVendor vendor = ModelBindingManager.getGlobalVendor();
265                return vendor == null ? a.equalsIgnoreCase(b)
266                                : SQLUtil.sameName(vendor, ESQLDataObjectType.dotTable, a, b);
267        }
268
269    /**
270     * Constructor for MDX and other non-SQL columns where only a name is available.
271     */
272    public TableColumn(Table table, String columnName) {
273        if (table == null || columnName == null)
274            throw new IllegalArgumentException("TableColumn arguments can't be null.");
275
276        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
277
278        this.startPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash());
279        this.endPosition = new Pair3<Long, Long, String>(-1L, -1L, ModelBindingManager.getGlobalHash());
280
281        this.name = columnName;
282        this.table = table;
283        table.addColumn(this);
284    }
285
286    public TableColumn(Table table, TConstant columnObject, int columnIndex) {
287        if (table == null || columnObject == null)
288            throw new IllegalArgumentException("TableColumn arguments can't be null.");
289
290        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
291
292        TSourceToken startToken = columnObject.getStartToken();
293        TSourceToken endToken = columnObject.getEndToken();
294        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
295                startToken.columnNo, ModelBindingManager.getGlobalHash());
296        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
297                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
298
299        this.name = columnObject.toString();
300        
301        this.table = table;
302        table.addColumn(this);
303    }
304    
305    public TableColumn(Table table, TConstant columnObject) {
306        if (table == null || columnObject == null)
307            throw new IllegalArgumentException("TableColumn arguments can't be null.");
308
309        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
310
311        TSourceToken startToken = columnObject.getStartToken();
312        TSourceToken endToken = columnObject.getEndToken();
313        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
314                startToken.columnNo, ModelBindingManager.getGlobalHash());
315        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
316                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
317
318        this.name = columnObject.toString();        
319        this.table = table;
320        table.addColumn(this);
321    }
322    
323    public TableColumn(Table table, TFunctionCall columnObject) {
324        if (table == null || columnObject == null)
325            throw new IllegalArgumentException("TableColumn arguments can't be null.");
326
327        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
328
329        TSourceToken startToken = columnObject.getStartToken();
330        TSourceToken endToken = columnObject.getEndToken();
331        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
332                startToken.columnNo, ModelBindingManager.getGlobalHash());
333        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
334                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
335
336        this.name = columnObject.toString();        
337        this.table = table;
338        table.addColumn(this);
339        }
340
341    
342    public TableColumn(Table table, TExpression columnObject, int columnIndex) {
343        if (table == null || columnObject == null)
344            throw new IllegalArgumentException("TableColumn arguments can't be null.");
345
346        id = ++ModelBindingManager.get().TABLE_COLUMN_ID;
347
348        TSourceToken startToken = columnObject.getStartToken();
349        TSourceToken endToken = columnObject.getEndToken();
350        this.startPosition = new Pair3<Long, Long, String>(startToken.lineNo,
351                startToken.columnNo, ModelBindingManager.getGlobalHash());
352        this.endPosition = new Pair3<Long, Long, String>(endToken.lineNo,
353                endToken.columnNo + SQLUtil.endTrim(endToken.getAstext()).length(), ModelBindingManager.getGlobalHash());
354
355                this.name = getColumnName(columnObject);
356        
357        this.table = table;
358        table.addColumn(this);
359    }
360    
361    protected String getColumnName(TExpression expr) {
362                if (expr.getExpressionType() == EExpressionType.simple_constant_t) {
363                        return expr.toString();
364                } else if (expr.getExpressionType() == EExpressionType.sqlserver_proprietary_column_alias_t) {
365                        // "alias = expression": no TAliasClause exists, but the left operand
366                        // is an alias by grammar, so it decodes like one (issue #704).
367                        return DlineageUtil.decodeAliasIdentifier(expr.getLeftOperand().toString());
368                } else if (expr.getExpressionType() == EExpressionType.function_t) {
369                        return expr.getFunctionCall().toString();
370                } else if (expr.getExpressionType() == EExpressionType.typecast_t) {
371                        return expr.getLeftOperand().toString();
372                } else if (expr.getExpressionType() == EExpressionType.parenthesis_t) {
373                        TExpression inner = expr.getLeftOperand();
374                        while (inner != null && inner.getExpressionType() == EExpressionType.parenthesis_t) {
375                                inner = inner.getLeftOperand();
376                        }
377                        return getColumnName(inner);
378                } else if (expr.getExpressionType() == EExpressionType.simple_object_name_t) {
379                        TObjectName columnName = expr.getObjectOperand();
380                        if (columnName.getPropertyToken() != null) {
381                                return DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(columnName));
382                        } else {
383                                return DlineageUtil.decodeColumnIdentifier(columnName.getColumnNameOnly());
384                        }
385                } else if (expr.getExpressionType() == EExpressionType.subquery_t) {
386                        TResultColumnList resultset = expr.getSubQuery().getResultColumnList();
387                        if (resultset != null && resultset.size() == 1) {
388                                TResultColumn resultColumn = resultset.getResultColumn(0);
389                                if (!SQLUtil.isEmpty(resultColumn.getColumnAlias())) {
390                                        return resultColumn.getColumnAlias();
391                                } else if (resultColumn.getExpr() != null) {
392                                        return getColumnName(resultColumn.getExpr());
393                                } else if (resultColumn.getColumnNameOnly() != null
394                                                && !"".equals(resultColumn.getColumnNameOnly())) {
395                                        if (resultColumn.getExpr().getObjectOperand() != null
396                                                        && resultColumn.getExpr().getObjectOperand().getPropertyToken() != null) {
397                                                this.name = DlineageUtil.decodeColumnIdentifier(DlineageUtil.fieldLeafText(resultColumn.getExpr().getObjectOperand()));
398                                        } else {
399                                                this.name = DlineageUtil.decodeColumnIdentifier(resultColumn.getColumnNameOnly());
400                                        }
401                                } else {
402                                        this.name = resultColumn.toString();
403                                }
404                        }
405                }
406                return quote(expr.toString());
407        }
408    
409 
410        private String quote(String column) {
411        EDbVendor vendor = ModelBindingManager.getGlobalVendor();
412        if (!DlineageUtil.isQuote(column)){
413                return SQLUtil.quoteDottedName(vendor, ESQLDataObjectType.dotColumn, column);
414        }
415        else return column;
416    }
417
418    public Table getTable() {
419        return table;
420    }
421
422    public long getId() {
423        return id;
424    }
425
426    public String getName() {
427        return name;
428    }
429
430    public Pair3<Long, Long, String> getStartPosition() {
431        return startPosition;
432    }
433
434    public Pair3<Long, Long, String> getEndPosition() {
435        return endPosition;
436    }
437
438    public void setStartPosition(Pair3<Long, Long, String> startPosition) {
439        this.startPosition = startPosition;
440    }
441
442    public void setEndPosition(Pair3<Long, Long, String> endPosition) {
443        this.endPosition = endPosition;
444    }
445
446    public TObjectName getColumnObject() {
447        return columnObject;
448    }
449
450    /**
451     * Adds the given star links as a map's {@code putAll} does: a link of a published name this column already has is
452     * replaced, a new one is appended. The map of another column ({@link #getStarLinkColumns()}) shares that column's
453     * links, as {@link #shareStarLinksFrom(Column)} does.
454     *
455     * @deprecated use {@link #shareStarLinksFrom(Column)}
456     */
457    @Deprecated
458    public void bindStarLinkColumns(Map<String, Set<TObjectName>> starLinkColumns) {
459        if (starLinkColumns != null && !starLinkColumns.isEmpty()) {
460            getStarLinkColumns().putAll(starLinkColumns);
461        }
462    }
463
464        @Override
465        void propagateStarLink(TObjectName objectName) {
466                if(this.getStarSourceColumns()!=null) {
467                        StarLinkKey nameKey = StarLinkStore.IDENTITY && !this.getStarSourceColumns().isEmpty()
468                                        ? StarLinkStore.keyOf(objectName) : null;
469                        for(Column column: this.getStarSourceColumns()) {
470                                boolean bind = true;
471                                if(column instanceof ResultColumn) {
472                                        ResultSet parent = ((ResultColumn) column).getResultSet();
473                                        for(ResultColumn item: parent.getColumns()) {
474                                                Boolean same = StarLinkStore.identityAnswer(item, nameKey);
475                                                if(same != null ? same.booleanValue() : DlineageUtil.sameColumnDisplay(item.getName(), objectName)) {
476                                                        bind = false;
477                                                        break;
478                                                }
479                                        }
480                                }
481                                if(column instanceof TableColumn) {
482                                        Table parent = ((TableColumn) column).getTable();
483                                        for(TableColumn item: parent.getColumns()) {
484                                                Boolean same = StarLinkStore.identityAnswer(item, nameKey);
485                                                if(same != null ? same.booleanValue() : DlineageUtil.sameColumnDisplay(item.getName(), objectName)) {
486                                                        bind = false;
487                                                        break;
488                                                }
489                                        }
490                                }
491                                if (bind) {
492                                        column.bindStarLinkColumn(objectName);
493                                }
494                        }
495                }
496        }
497
498        public boolean isShowStar() {
499                return showStar;
500        }
501
502        public void setShowStar(boolean showStar) {
503                this.showStar = showStar;
504        }
505        
506        public Table getView() {
507                return getTable();
508        }
509
510        public Integer getColumnIndex() {
511                return columnIndex;
512        }
513
514        public boolean isVariant() {
515                return isVariant;
516        }
517
518        public void setVariant(boolean isVariant) {
519                this.isVariant = isVariant;
520        }
521
522        public String getDisplayName() {
523                return displayName;
524        }
525
526        public void setDisplayName(String displayName) {
527                this.displayName = displayName;
528        }
529
530        public void setExpandStar(boolean expandStar) {
531                this.expandStar = expandStar;
532        }
533
534        public boolean isExpandStar() {
535                return expandStar;
536        }
537
538        public boolean isPseduo() {
539                return isPseduo;
540        }
541
542        public void setPseduo(boolean isPseduo) {
543                this.isPseduo = isPseduo;
544        }
545
546        public void notBindStarLinkColumn(boolean notBindStarColumn) {
547                this.notBindStarColumn = notBindStarColumn;
548        }
549        
550        public boolean isNotBindStarLinkColumn() {
551                return notBindStarColumn;
552        }
553
554    public Boolean getPrimaryKey() {
555        return primaryKey;
556    }
557
558    public void setPrimaryKey(Boolean primaryKey) {
559        this.primaryKey = primaryKey;
560    }
561
562    public Boolean getUnqiueKey() {
563        return unqiueKey;
564    }
565
566    public void setUnqiueKey(Boolean unqiueKey) {
567        this.unqiueKey = unqiueKey;
568    }
569
570    public Boolean getIndexKey() {
571        return indexKey;
572    }
573
574    public void setIndexKey(Boolean indexKey) {
575        this.indexKey = indexKey;
576    }
577
578    public Boolean getForeignKey() {
579        return foreignKey;
580    }
581
582    public void setForeignKey(Boolean foreignKey) {
583        this.foreignKey = foreignKey;
584    }
585
586    public String getDataType() {
587        return dataType;
588    }
589
590    public void setDataType(String dataType) {
591        this.dataType = dataType;
592    }
593    
594    @Override
595    public String toString() {
596        return this.name;
597    }
598
599        public void setColumnIndex(int columnIndex) {
600                this.columnIndex = columnIndex;
601        }
602
603        public boolean isStruct() {
604                return isStruct;
605        }
606
607        public void setStruct(boolean isStruct) {
608                this.isStruct = isStruct;
609        }
610
611        public List<Transform> getTransforms() {
612                return transforms;
613        }
614
615        public void setTransform(Transform transform) {
616                if(transform==null) {
617                        return;
618                }
619                if(transforms == null) {
620                        transforms = new ArrayList<Transform>();
621                }
622                if(!transforms.contains(transform)) {
623                        transforms.add(transform);
624                }
625        }
626
627        public List<Object> getCandidateParents() {
628                return candidateParents;
629        }
630
631        public void setCandidateParents(List<Object> candidateParents) {
632                if (this.candidateParents == null) {
633                        this.candidateParents = candidateParents;
634                }
635                else {
636                        this.candidateParents.addAll(candidateParents);
637                }
638        }
639
640        public TableColumn getSourceColumn() {
641                return sourceColumn;
642        }
643
644        /** Whether this is a field path node (a field or an array element of a column), not a column of its own. */
645        public boolean isFieldPathNode() {
646                return fieldPath != null;
647        }
648
649        /** For a field path node, the node it is a field or an array element of; null for a column. */
650        public TableColumn getParentColumn() {
651                return parentColumn;
652        }
653
654        /** For a field path node, its path under the declared column; null for a column. */
655        public gudusoft.gsqlparser.common.structured.StructuredColumnPath getFieldPath() {
656                return fieldPath;
657        }
658
659        /**
660         * The fields of this column or field as its declared STRUCT type lists them, each a field path node, in order;
661         * empty when no declared type lists them, so the fields are not known.
662         */
663        public List<TableColumn> getDeclaredFields() {
664                List<TableColumn> fields = new ArrayList<TableColumn>();
665                FieldDeclaration declaration = getTable().getActiveFieldDeclaration();
666                if (declaration == null || !declaration.declaresFieldsOf(this)) {
667                        return fields;
668                }
669                for (TableColumn node : children) {
670                        if (declaration.declares(node) && node.fieldPath.getSegments()
671                                        .get(node.fieldPath.getSegments().size() - 1)
672                                        .getKind() == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.FIELD) {
673                                fields.add(node);
674                        }
675                }
676                return fields;
677        }
678
679        /** The node of the element of this column or field when a declared ARRAY type made one, {@code items[*]}; else null. */
680        public TableColumn getDeclaredElement() {
681                FieldDeclaration declaration = getTable().getActiveFieldDeclaration();
682                if (declaration == null) {
683                        return null;
684                }
685                for (TableColumn node : children) {
686                        if (declaration.declares(node) && node.fieldPath.getSegments()
687                                        .get(node.fieldPath.getSegments().size() - 1)
688                                        .getKind() == gudusoft.gsqlparser.common.structured.StructuredPathSegmentKind.ARRAY_ELEMENT) {
689                                return node;
690                        }
691                }
692                return null;
693        }
694
695        /**
696         * Whether the STRUCT type this column or field is declared with lists its fields, each a field path node, under
697         * the declaration in force at the statement being analyzed ({@link Table#getActiveFieldDeclaration()}).
698         */
699        public boolean isFieldsDeclared() {
700                FieldDeclaration declaration = getTable().getActiveFieldDeclaration();
701                return declaration != null && declaration.declaresFieldsOf(this);
702        }
703
704        /** Records, in the declaration being made, that this column's or field's declared STRUCT type lists its fields. */
705        public void setFieldsDeclared(boolean fieldsDeclared) {
706                if (fieldsDeclared) {
707                        getTable().fieldDeclarationForWrite().addFieldsDeclared(this);
708                }
709        }
710
711        /** Records, in the declaration being made, that a declared type made this field path node. */
712        public void setDeclaredField(boolean declaredField) {
713                if (declaredField) {
714                        getTable().fieldDeclarationForWrite().addDeclaredNode(this);
715                }
716        }
717
718        /** Whether the declaration in force made this field path node, rather than a read. */
719        public boolean isDeclaredField() {
720                FieldDeclaration declaration = getTable().getActiveFieldDeclaration();
721                return declaration != null && declaration.declares(this);
722        }
723
724        /** The kind of type the DDL declares this column or field with; null when no DDL declares it. */
725        public gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind getDeclaredKind() {
726                return declaredKind;
727        }
728
729        public void setDeclaredKind(gudusoft.gsqlparser.common.structured.DeclaredTypeNode.Kind declaredKind) {
730                this.declaredKind = declaredKind;
731        }
732
733        /** The declared column a field path node belongs to, the column itself for a column. */
734        public TableColumn getDeclaredColumn() {
735                TableColumn column = this;
736                while (column.parentColumn != null) {
737                        column = column.parentColumn;
738                }
739                return column;
740        }
741
742        public void setSourceColumn(TableColumn sourceColumn) {
743                this.sourceColumn = sourceColumn;
744        }
745}