001package gudusoft.gsqlparser.nodes;
002
003import gudusoft.gsqlparser.*;
004import gudusoft.gsqlparser.compiler.TVariable;
005import gudusoft.gsqlparser.resolver.TColumnTableMatch;
006import gudusoft.gsqlparser.sqlenv.*;
007import gudusoft.gsqlparser.util.keywordChecker;
008
009import java.util.ArrayList;
010
011/**
012 * The qualified or unqualified name that identifies a database object.
013 * The qualified name may includes those parts: server,database,schema,object,part and dblink.
014 * This class represents database object in different database vendors such as Oracle, SQL Server in a uniform way.
015 * <p>
016 * The general syntax of database object in Oracle: [schema.]object[.part][@dblink]
017 * <p>
018 * The general syntax of database object in SQL Server: [server.][database.][schema.]object
019 * <p>
020 * The meaning of {@link #getObjectToken()} and {@link #getPartToken()} depends on the {@link #getDbObjectType()}.
021 * If this database object is a schema object such as table, index, then the objectToken represents this
022 * database object and partToken is null.
023 * <p><p>
024 * If this TObjectName represents a column, the partToken represents the column name, the objectToken is table/view
025 * name of this column if this column is qualified like <code>table.column</code>, otherwise, the objectToken is
026 * null.
027 * <p>
028 * schemaToken, databaseToken, serverToken is the qualified part of a database object name.
029 * If this objectName represents a database name in the create database statement like this
030 * <code>CREATE DATABASE menagerie</code>, then, the objectToken is menagerie and databaseToken is null.
031 *
032 * @see gudusoft.gsqlparser.EDbObjectType
033 **/
034
035public class TObjectName extends TParseTreeNode implements Cloneable{
036
037    public void setOwnStmt(TCustomSqlStatement ownStmt) {
038        this.ownStmt = ownStmt;
039    }
040
041    public TCustomSqlStatement getOwnStmt() {
042        return ownStmt;
043    }
044
045    private TCustomSqlStatement ownStmt;
046
047
048    private TExceptReplaceClause exceptReplaceClause;
049
050    public TExceptReplaceClause getExceptReplaceClause() {
051        return exceptReplaceClause;
052    }
053
054    public void setExceptReplaceClause(TExceptReplaceClause exceptReplaceClause) {
055        this.exceptReplaceClause = exceptReplaceClause;
056    }
057        
058    private ETableKind tableKind = ETableKind.etkBase;
059
060    public void setTableKind(ETableKind tableKind) {
061        this.tableKind = tableKind;
062    }
063
064    public ETableKind getTableKind() {
065        return tableKind;
066    }
067
068    private EPseudoTableType pseudoTableType = EPseudoTableType.none;
069
070    public EPseudoTableType getPseudoTableType() {
071        return pseudoTableType;
072    }
073
074    public void setPseudoTableType(EPseudoTableType pseudoTableType) {
075        this.pseudoTableType = pseudoTableType;
076    }
077
078    TVariable linkedVariable = null;
079
080    public void setLinkedVariable(TVariable linkedVariable) {
081        this.linkedVariable = linkedVariable;
082        this.dbObjectType = EDbObjectType.variable;
083    }
084
085    public TVariable getLinkedVariable() {
086        return linkedVariable;
087    }
088
089    // 如果该对象表示一个字段,那么本属性表示该字段的数据来自那个源字段
090    private TAttributeNode sourceAttributeNode;
091
092    /**
093     * Sets the source attribute node for this object name.
094     * <p>
095     * This method is used only by the legacy TSQLResolver (RESOLVER).
096     * It is not used when using TSQLResolver2 (RESOLVER2) for name resolution.
097     * For RESOLVER2, use {@link #setResolution(gudusoft.gsqlparser.resolver2.model.ResolutionResult)} instead.
098     *
099     * @param sourceAttributeNode the attribute node representing the resolved source
100     * @deprecated Since 3.4.0.5. Use TSQLResolver2 with {@link #setResolution} instead.
101     */
102    @Deprecated
103    public void setSourceAttributeNode(TAttributeNode sourceAttributeNode) {
104        this.sourceAttributeNode = sourceAttributeNode;
105    }
106
107    /**
108     * Returns the source attribute node for this object name.
109     * <p>
110     * This method is used only by the legacy TSQLResolver (RESOLVER).
111     * It is not used when using TSQLResolver2 (RESOLVER2) for name resolution.
112     * For RESOLVER2, use {@link #getResolution()} instead.
113     *
114     * @return the attribute node representing the resolved source, or null if not resolved
115     * @deprecated Since 3.4.0.5. Use TSQLResolver2 with {@link #getResolution()} instead.
116     */
117    @Deprecated
118    public TAttributeNode getSourceAttributeNode() {
119        return sourceAttributeNode;
120    }
121
122    // ===== New Resolver2 fields =====
123    /**
124     * Resolution result from the new resolver (resolver2).
125     * Contains complete resolution status, column source, and candidate information.
126     * This is set by gudusoft.gsqlparser.resolver2.NameResolver
127     */
128    private gudusoft.gsqlparser.resolver2.model.ResolutionResult resolution;
129
130    /**
131     * Side-channel hint for deep struct field access (3+ part names without alias).
132     * Unlike resolution, this does NOT trigger sourceTable binding or affect lineage topology.
133     * Set by NameResolver for BigQuery 3-part no-alias struct patterns.
134     *
135     * @see gudusoft.gsqlparser.resolver2.model.StructFieldHint
136     */
137    private gudusoft.gsqlparser.resolver2.model.StructFieldHint structFieldHint;
138
139    /**
140     * Set resolution result (called by resolver2.NameResolver)
141     */
142    public void setResolution(gudusoft.gsqlparser.resolver2.model.ResolutionResult resolution) {
143        this.resolution = resolution;
144
145        // Synchronize sourceTable with resolution result
146        // sourceTable represents the IMMEDIATE source table (subquery, CTE, or physical table)
147        // NOT the final physical table after tracing through subqueries/CTEs
148        if (resolution != null && resolution.getStatus() == gudusoft.gsqlparser.resolver2.ResolutionStatus.EXACT_MATCH) {
149            gudusoft.gsqlparser.resolver2.model.ColumnSource source = resolution.getColumnSource();
150            if (source != null) {
151                // Get the immediate source table from the namespace that resolved this column
152                gudusoft.gsqlparser.resolver2.namespace.INamespace sourceNs = source.getSourceNamespace();
153                TTable immediateSource = (sourceNs != null) ? sourceNs.getSourceTable() : null;
154
155                // For star columns, preserve the existing sourceTable set by linkColumnToTable
156                // Star columns represent ALL columns from all tables in FROM clause
157                String colName = this.getColumnNameOnly();
158                if (colName != null && colName.equals("*")) {
159                    // Don't update sourceTable for star columns
160                } else if (immediateSource != null) {
161                    // Special case: if Phase 1 (linkColumnToTable) already resolved to a physical table,
162                    // and Phase 2 resolves to a CTE/subquery, check if they're referring to the same
163                    // underlying table. If so, preserve Phase 1's physical table reference.
164                    // This handles cases like non-recursive CTEs where a table reference inside the CTE
165                    // definition should resolve to the physical table, not the CTE itself.
166                    boolean preservePhase1 = false;
167                    if (this.sourceTable != null && immediateSource != this.sourceTable) {
168                        // Phase 1 already set sourceTable
169                        ETableSource phase1Type = this.sourceTable.getTableType();
170                        boolean phase1IsPhysical = (phase1Type == ETableSource.objectname);
171
172                        // Check if Phase 2 resolved to CTE/subquery
173                        boolean phase2IsCTEOrSubquery = immediateSource.isCTEName()
174                            || immediateSource.getTableType() == ETableSource.subquery;
175
176                        if (phase1IsPhysical && phase2IsCTEOrSubquery) {
177                            // Get finalTable from Phase 2 resolution
178                            TTable finalTable = source.getFinalTable();
179                            // If finalTable matches Phase 1's sourceTable (same physical table),
180                            // preserve Phase 1's result - BUT only for non-alias columns.
181                            // For alias columns (e.g., SELECT t.id AS col1), the subquery layer
182                            // must be preserved because the alias name doesn't exist in the physical table.
183                            // We use getFinalColumnName() as a proxy: if it returns non-null, the column
184                            // is an alias or passthrough-to-alias with a different name.
185                            if (finalTable == this.sourceTable
186                                    && !source.isColumnAlias()
187                                    && source.getFinalColumnName() == null) {
188                                preservePhase1 = true;
189                            }
190                        }
191                    }
192
193                    if (!preservePhase1) {
194                        // Set sourceTable to the immediate source (subquery, CTE, or physical table)
195                        this.sourceTable = immediateSource;
196                    }
197                }
198
199                // Populate candidateTables from ColumnSource for UNION columns
200                // When getFinalTable() is null but we have candidate tables (from UNION branches),
201                // these should be tracked so the formatter can output all candidates
202                java.util.List<TTable> sourceCandidates = source.getCandidateTables();
203                if (sourceCandidates != null && !sourceCandidates.isEmpty()) {
204                    if (candidateTables == null) {
205                        candidateTables = new TTableList();
206                    }
207                    for (TTable candidate : sourceCandidates) {
208                        if (candidate != null && !containsTable(candidateTables, candidate)) {
209                            candidateTables.addTable(candidate);
210                        }
211                    }
212                    // Check if candidates came from UNION/CTE propagation (not ambiguity)
213                    // Evidence containing "union" or "propagate" indicates UNION branch tracing
214                    String evidence = source.getEvidence();
215                    if (evidence != null && (evidence.contains("union") || evidence.contains("propagate"))) {
216                        candidatesFromUnion = true;
217                    }
218                }
219
220                // Sync propertyToken for struct field access (backward compatibility)
221                // When resolver2 detects struct field access (e.g., info.name where info is a RECORD column),
222                // call columnToProperty() to shift tokens so propertyToken contains the field name.
223                //
224                // To avoid false positives (e.g., treating table.column as column.field when table not found),
225                // we verify the base column is STRUCT type through:
226                // 1. Semantic analysis: Check if ColumnSource's definition node has STRUCT datatype
227                // 2. SQLEnv fallback: Check external metadata if semantic analysis inconclusive
228                // 3. TableNamespace verification: If base column comes from a physical table, it's STRUCT
229                //
230                // Method 3 is key: if "info" resolves to a column in a real table (TableNamespace),
231                // then "info.name" MUST be STRUCT field access (otherwise the SQL would be invalid).
232                // This enables pure SQL-based STRUCT detection without DDL or metadata.
233                if (source.isStructFieldAccess() && source.hasFieldPath()) {
234                    String baseColumnName = source.getExposedName();
235                    boolean isVerifiedStructColumn = false;
236
237                    // Method 1: Semantic analysis - check ColumnSource's definition node for STRUCT type
238                    // This works when DDL (CREATE TABLE with STRUCT columns) is parsed in the same batch
239                    TParseTreeNode definitionNode = source.getDefinitionNode();
240                    if (definitionNode instanceof TColumnDefinition) {
241                        TColumnDefinition colDef = (TColumnDefinition) definitionNode;
242                        TTypeName datatype = colDef.getDatatype();
243                        if (datatype != null && datatype.getDataType() == EDataType.struct_t) {
244                            isVerifiedStructColumn = true;
245                        }
246                    }
247
248                    // Method 2: SQLEnv fallback - check external metadata if semantic analysis didn't verify
249                    if (!isVerifiedStructColumn && this.sqlEnv != null && baseColumnName != null && immediateSource != null) {
250                        String tableName = immediateSource.getFullName();
251                        if (tableName != null) {
252                            gudusoft.gsqlparser.sqlenv.TSQLTable sqlTable = this.sqlEnv.searchTable(tableName);
253                            if (sqlTable != null) {
254                                gudusoft.gsqlparser.sqlenv.TSQLColumn sqlColumn = sqlTable.getColumn(baseColumnName);
255                                if (sqlColumn != null && sqlColumn.getColumnDataType() != null) {
256                                    EDataType dataType = sqlColumn.getColumnDataType().getDataType();
257                                    isVerifiedStructColumn = (dataType == EDataType.struct_t);
258                                }
259                            }
260                        }
261                    }
262
263                    // Method 3: TableNamespace verification - if base column comes from a physical table
264                    // When struct field fallback finds the base column (e.g., "info") in a TableNamespace,
265                    // the SQL semantic guarantees it's a STRUCT column. If "info" is a regular column in
266                    // table "student_records", then "info.name" can ONLY mean STRUCT field access.
267                    // This enables STRUCT detection purely from SELECT statement semantics.
268                    //
269                    // DISABLED: This optimization causes regressions in complex queries where column names
270                    // overlap with table aliases (e.g., UNION subqueries with NULL AS column_name).
271                    // Users can access field names via source.getFieldPath().getFirst() instead.
272                    // TODO: Implement smarter heuristic to detect truly "clear" semantics.
273                    //
274                    // if (!isVerifiedStructColumn && sourceNs instanceof gudusoft.gsqlparser.resolver2.namespace.TableNamespace) {
275                    //     // Base column comes from a physical table - definitely STRUCT access
276                    //     isVerifiedStructColumn = true;
277                    // }
278
279                    // Only call columnToProperty() for verified STRUCT columns and non-star columns
280                    if (isVerifiedStructColumn) {
281                        String currentColName = getColumnNameOnly();
282                        if (currentColName == null || !currentColName.equals("*")) {
283                            columnToProperty();
284                        }
285                    }
286                }
287            }
288        }
289
290        // Sync resolveStatus for backward compatibility with legacy code (e.g., TGetTableColumn)
291        if (resolution != null) {
292            switch (resolution.getStatus()) {
293                case EXACT_MATCH:
294                    this.resolveStatus = TBaseType.RESOLVED_AND_FOUND;
295                    break;
296                case AMBIGUOUS:
297                    this.resolveStatus = TBaseType.RESOLVED_BUT_AMBIGUOUS;
298                    break;
299                case NOT_FOUND:
300                    // Leave as NOT_RESOLVED_YET or whatever it was before
301                    break;
302            }
303        }
304    }
305
306    /**
307     * Helper to check if a table is already in the TTableList
308     */
309    private static boolean containsTable(TTableList list, TTable table) {
310        if (list == null || table == null) return false;
311        for (int i = 0; i < list.size(); i++) {
312            TTable existing = list.getTable(i);
313            if (existing == table) return true;
314        }
315        return false;
316    }
317
318    /**
319     * Get resolution result from new resolver
320     */
321    public gudusoft.gsqlparser.resolver2.model.ResolutionResult getResolution() {
322        return resolution;
323    }
324
325    /**
326     * Convenience method: get column source (most common access pattern)
327     */
328    public gudusoft.gsqlparser.resolver2.model.ColumnSource getColumnSource() {
329        if (resolution == null) return null;
330
331        if (resolution.getStatus() == gudusoft.gsqlparser.resolver2.ResolutionStatus.EXACT_MATCH) {
332            return resolution.getColumnSource();
333        } else if (resolution.getStatus() == gudusoft.gsqlparser.resolver2.ResolutionStatus.AMBIGUOUS) {
334            // For ambiguous: return first candidate (configurable behavior)
335            gudusoft.gsqlparser.resolver2.model.AmbiguousColumnSource ambiguous = resolution.getAmbiguousSource();
336            if (ambiguous != null && ambiguous.getCandidateCount() > 0) {
337                return ambiguous.getCandidates().get(0);
338            }
339        }
340
341        return null;
342    }
343
344    /**
345     * Get the struct field hint for deep struct access (3+ part names without alias).
346     * This is a side-channel annotation that does NOT affect resolution or sourceTable.
347     *
348     * @return The struct field hint, or null if not a deep struct access
349     */
350    public gudusoft.gsqlparser.resolver2.model.StructFieldHint getStructFieldHint() {
351        return structFieldHint;
352    }
353
354    /**
355     * Set the struct field hint (called by NameResolver for 3+ part no-alias struct patterns).
356     */
357    public void setStructFieldHint(gudusoft.gsqlparser.resolver2.model.StructFieldHint hint) {
358        this.structFieldHint = hint;
359    }
360
361    /**
362     * Check if this column reference is ambiguous
363     */
364    public boolean isAmbiguous() {
365        return resolution != null &&
366               resolution.getStatus() == gudusoft.gsqlparser.resolver2.ResolutionStatus.AMBIGUOUS;
367    }
368
369    /**
370     * Check if this column reference has been resolved (by new resolver)
371     */
372    public boolean isResolved() {
373        return resolution != null &&
374               resolution.getStatus() != gudusoft.gsqlparser.resolver2.ResolutionStatus.NOT_FOUND;
375    }
376
377    /**
378     * Get all candidate tables (for ambiguous columns)
379     */
380    public java.util.List<TTable> getCandidateTables2() {
381        if (!isAmbiguous()) return java.util.Collections.emptyList();
382
383        gudusoft.gsqlparser.resolver2.model.AmbiguousColumnSource ambiguous = resolution.getAmbiguousSource();
384        return ambiguous.getCandidates().stream()
385            .map(gudusoft.gsqlparser.resolver2.model.ColumnSource::getFinalTable)
386            .filter(java.util.Objects::nonNull)
387            .collect(java.util.stream.Collectors.toList());
388    }
389    // ===== End of New Resolver2 fields =====
390//    private boolean isResolved = false;
391//
392//    public void setResolved(boolean resolved) {
393//        isResolved = resolved;
394//    }
395//
396//    public void setResolvedRelation(TTable resolvedRelation) {
397//        this.resolvedRelation = resolvedRelation;
398//        this.isResolved = true;
399//    }
400//
401//    public boolean isResolved() {
402//        return isResolved;
403//    }
404//
405//    public TTable getResolvedRelation() {
406//        return resolvedRelation;
407//    }
408//
409//    private TTable resolvedRelation = null;
410
411    public TObjectName clone(){
412        TObjectName cloneObject = new TObjectName();
413        cloneObject.dbObjectType = this.dbObjectType;
414        cloneObject.dbvendor = this.dbvendor;
415
416        if (this.partToken != null){
417            cloneObject.partToken = this.partToken.clone();
418        }
419        if (this.objectToken != null){
420            cloneObject.objectToken = this.objectToken.clone();
421        }
422        if (this.schemaToken != null){
423        cloneObject.schemaToken = this.schemaToken.clone();
424        }
425
426        if (this.databaseToken != null){
427            cloneObject.databaseToken = this.databaseToken.clone();
428        }
429
430        if (this.serverToken != null){
431            cloneObject.serverToken = this.serverToken.clone();
432        }
433
434        if (this.propertyToken != null){
435            cloneObject.propertyToken = this.propertyToken.clone();
436        }
437
438        if (this.methodToken != null){
439            cloneObject.methodToken = this.methodToken.clone();
440        }
441
442        if (this.packageToken != null){
443            cloneObject.packageToken = this.packageToken.clone();
444        }
445
446        cloneObject.numberOfPart = this.numberOfPart;
447
448        // Clone additionalParts for deeply nested struct access
449        if (this.additionalParts != null && !this.additionalParts.isEmpty()) {
450            cloneObject.additionalParts = new java.util.ArrayList<>();
451            for (TSourceToken token : this.additionalParts) {
452                cloneObject.additionalParts.add(token.clone());
453            }
454        }
455
456        // Copy startToken and endToken from parent TParseTreeNode
457        if (this.getStartToken() != null) {
458            cloneObject.setStartTokenDirectly(this.getStartToken());
459        }
460        if (this.getEndToken() != null) {
461            cloneObject.setEndTokenDirectly(this.getEndToken());
462        }
463
464        return cloneObject;
465    }
466
467    private TObjectName parentObjectName;
468
469    public void setParentObjectName(TObjectName parentObjectName) {
470        this.parentObjectName = parentObjectName;
471    }
472
473    @Override
474    public TObjectName getParentObjectName() {
475        return parentObjectName;
476    }
477
478    public void setPath(TPathSqlNode path) {
479        this.path = path;
480        this.setEndToken(path.getEndToken());
481    }
482
483    /**
484     *  stage path
485     *
486     * @return
487     */
488    public TPathSqlNode getPath() {
489        return path;
490    }
491
492    private TPathSqlNode path;
493
494//    private TObjectName cursorName;
495//
496//    public void setCursorName(TObjectName cursorName) {
497//        this.cursorName = cursorName;
498//    }
499//
500//    /**
501//     * related cursor name if oracle for statement
502//     * like: FOR emp_rec IN emp_cur
503//     * @return
504//     */
505//    public TObjectName getCursorName() {
506//        return cursorName;
507//    }
508
509    private ArrayList<TTable> sourceTableList;
510
511    /**
512     * source table list for star column,
513     * <br>select * from emp,dept
514     * <br> * column will be list to both emp and dept table.
515     * <br>
516     * @return
517     */
518    public ArrayList<TTable> getSourceTableList() {
519        if (sourceTableList == null) {
520            sourceTableList = new ArrayList<>();
521        }
522        return sourceTableList;
523    }
524
525    private boolean isImplicitSchema = false;
526    private boolean isImplicitDatabase = false;
527
528    public boolean isImplicitSchema() {
529        return isImplicitSchema;
530    }
531
532    public boolean isImplicitDatabase() {
533        return isImplicitDatabase;
534    }
535
536//    public void setOriginalQuery(TSelectSqlStatement originalQuery) {
537//        this.originalQuery = originalQuery;
538//    }
539//
540//    public TSelectSqlStatement getOriginalQuery() {
541//        return originalQuery;
542//    }
543//
544//    private TSelectSqlStatement originalQuery = null;
545
546    private ArrayList<TAttributeNode> attributeNodesDerivedFromFromClause;
547
548    /**
549     * 这个属性只有当 column 为 * 时有效
550     * 当 column 为 * 时, 本属性包含该 * 展开后对应的 attributeNode 列表,来源是 FROM CLAUSE中的 tables, 在 resolve star column
551     * 时给本属性赋值, TStmtScope.resolve(TObjectName objectName)
552     *
553     * 当 table 有metadata或DDL给出了明确的字段时,每table个展开的 attributeNode 包含明确的字段名,such as t.c
554     * 当 table 没有 metadata 和 DDL 时,每table个只展开的 一个 attributeNode,内容为 t.*
555     *
556     *
557     * @return
558     */
559    public ArrayList<TAttributeNode> getAttributeNodesDerivedFromFromClause() {
560        if (attributeNodesDerivedFromFromClause == null){
561            attributeNodesDerivedFromFromClause = new ArrayList<TAttributeNode>();
562        }
563        return attributeNodesDerivedFromFromClause;
564    }
565
566    private ArrayList<TColumnTableMatch> candidateAttributeNodes;
567
568    /**
569     * 非 star column 使用该属性存放可能包含该 column 的 attributeNode
570     * star column 使用 {@link #getAttributeNodesDerivedFromFromClause()}
571     *
572     * @return
573     */
574    public ArrayList<TColumnTableMatch> getCandidateAttributeNodes() {
575        if (candidateAttributeNodes == null){
576            candidateAttributeNodes = new ArrayList<TColumnTableMatch>();
577        }
578        return candidateAttributeNodes;
579    }
580
581    private TTableList candidateTables = null;
582
583    public TTableList getCandidateTables() {
584        if (candidateTables == null){
585            candidateTables = new TTableList();
586        }
587        return candidateTables;
588    }
589
590    /** True if candidateTables came from UNION/CTE branch propagation, not from ambiguity */
591    private boolean candidatesFromUnion = false;
592
593    /**
594     * Returns true if candidate tables came from UNION/CTE branch propagation,
595     * false if they came from ambiguity (e.g., unqualified column in multi-table query).
596     * When true, the formatter should output all candidates instead of marking as "missed".
597     */
598    public boolean isCandidatesFromUnion() {
599        return candidatesFromUnion;
600    }
601
602    /**
603     * Check DDL verification status for a candidate table.
604     *
605     * <p>Returns a tri-state result:</p>
606     * <ul>
607     *   <li>1 = Column exists in table's DDL</li>
608     *   <li>0 = Column NOT found in table's DDL (DDL available but column missing)</li>
609     *   <li>-1 = Cannot verify (no DDL available for this table)</li>
610     * </ul>
611     *
612     * @param table The candidate table to check
613     * @return DDL verification status: 1 (exists), 0 (not found), -1 (no DDL)
614     */
615    public int getDdlVerificationStatus(TTable table) {
616        String columnName = this.getColumnNameOnly();
617        return gudusoft.gsqlparser.resolver2.model.ColumnSource.getDdlVerificationStatus(table, columnName);
618    }
619
620    /**
621     * Get DDL verification status for all candidate tables.
622     *
623     * <p>Returns a map from each candidate table to its DDL verification status:</p>
624     * <ul>
625     *   <li>1 = Column exists in table's DDL</li>
626     *   <li>0 = Column NOT found in table's DDL</li>
627     *   <li>-1 = Cannot verify (no DDL available)</li>
628     * </ul>
629     *
630     * @return Map of candidate tables to their DDL verification status, or empty map if no candidates
631     */
632    public java.util.Map<TTable, Integer> getCandidateTableDdlStatus() {
633        java.util.Map<TTable, Integer> result = new java.util.LinkedHashMap<>();
634        if (candidateTables == null || candidateTables.size() == 0) {
635            return result;
636        }
637
638        String columnName = this.getColumnNameOnly();
639        for (int i = 0; i < candidateTables.size(); i++) {
640            TTable candidate = candidateTables.getTable(i);
641            if (candidate != null) {
642                int status = gudusoft.gsqlparser.resolver2.model.ColumnSource.getDdlVerificationStatus(candidate, columnName);
643                result.put(candidate, status);
644            }
645        }
646        return result;
647    }
648
649    private boolean isOrphanColumn = false;
650
651    public void setOrphanColumn(boolean orphanColumn) {
652        isOrphanColumn = orphanColumn;
653    }
654
655    public boolean isOrphanColumn() {
656        return isOrphanColumn;
657    }
658
659    private boolean isReservedKeyword = false;
660
661    public boolean isReservedKeyword() {
662        return isReservedKeyword;
663    }
664
665    private TColumnDefinition linkedColumnDef = null;
666
667    public void setLinkedColumnDef(TColumnDefinition linkedColumnDef) {
668        this.linkedColumnDef = linkedColumnDef;
669    }
670
671    /**
672     * The column definition in create/alter table statement that include this column name object.
673     * <pre>
674     *     CREATE TABLE table_name (
675     *       column1 datatype,
676     *       column2 datatype
677     *    );
678     * </pre>
679     * In above SQL, <code>column1 datatype</code> is the column definition while <code>column1</code> is this
680     * object name.
681     * @return column definition in create/alter table statement
682     */
683    public TColumnDefinition getLinkedColumnDef() {
684
685        return linkedColumnDef;
686    }
687
688    private TObjectName namespace;
689
690    public void setNamespace(TObjectName namespace) {
691        this.namespace = namespace;
692    }
693
694    /**
695     * The Couchbase namespace before keyspace
696     * @return the namespace
697     */
698    public TObjectName getNamespace() {
699
700        return namespace;
701    }
702
703    /**
704     * 返回该对象名的字符串表示(中文说明):
705     * <p>
706     * 1) 优先调用父类 {@link TBaseType#toString()} 获取已构造好的整体标识文本;
707     * 若父类返回非空,则:
708     *   - 对于 Snowflake,当文本以 <code>IDENTIFIER(...)</code> 形式出现时,仅提取并返回其中的
709     *     字面量部分(去除引号),以符合 Snowflake 标识符解析规则;
710     *   - 其他情况直接返回父类结果。
711     * <p>
712     * 2) 若父类返回为空(尚未生成整体文本),则回退到更细粒度的 token:
713     *   - 若存在 <code>part</code> 级 token,返回其字符串;
714     *   - 否则若存在 <code>object</code> 级 token,返回其字符串;
715     *   - 若仍不存在,返回 null。
716     * <p>
717     * 目的:统一并优先复用已构造的字符串表示,同时兼容特定数据库(如 Snowflake)的
718     * 标识符语义,从而在不同厂商 SQL 中提供稳定、符合预期的对象名输出。
719     */
720    public String toString() {
721        String ret = super.toString();
722
723        if (ret != null) {
724            if ((dbvendor == EDbVendor.dbvsnowflake) && (ret.toString().toLowerCase().startsWith("identifier("))){
725                // snowflake identifier name: IDENTIFIER( { string_literal | session_variable | bind_variable | snowflake_scripting_variable } )
726                // only return the string_literal part
727                // https://www.sqlparser.com/bugs/mantisbt/view.php?id=3566
728                // When parts have been split (mantis #4237), reconstruct the full qualified name
729                StringBuilder sb = new StringBuilder();
730                if (getDatabaseString() != null && !getDatabaseString().isEmpty()) {
731                    sb.append(getDatabaseString()).append(".");
732                }
733                if (getSchemaString() != null && !getSchemaString().isEmpty()) {
734                    sb.append(getSchemaString()).append(".");
735                }
736                sb.append(getObjectString());
737                return sb.toString();
738            }
739            return ret;
740        }
741
742        if (getPartToken() != null) return getPartString();
743        if (getObjectToken() != null ) return getObjectString();
744
745        return  null;
746    }
747
748    public void setQuoteType(EQuoteType quoteType) {
749        this.quoteType = quoteType;
750    }
751
752    /**
753     * Tell whether this is a quoted objectName.
754     * @return EQuoteType.squareBracket or EQuoteType.doubleQuote if this objectName is quoted.
755     */
756    public EQuoteType getQuoteType() {
757        if (toString().startsWith("[")){
758            return EQuoteType.squareBracket;
759        }else if (toString().startsWith("\"")){
760            return EQuoteType.doubleQuote;
761        }else if (toString().startsWith("`")){
762            return EQuoteType.backtick;
763        }else
764            return quoteType;
765    }
766
767    private EQuoteType quoteType = EQuoteType.notQuoted;
768//    private String stringValue;
769
770
771
772    /**
773     * Internal use only
774     */
775    public int searchLevel = 0;
776
777    public boolean isContinueToSearch(){
778        // if column is in where clause, we can search 10 levels up
779
780        // if column is in select list, only select one level up.
781        // only search one level up, c:\prg\gsp_sqlfiles\TestCases\java\oracle\dbobject\berger_sqltest_04.sql
782
783        if (this.getLocation() == ESqlClause.where) return (searchLevel < 10);
784        else return (searchLevel < 1);
785    }
786    private TResultColumn sourceColumn;
787
788    /**
789     * Set the result column which include this column name. Used by parser internally.
790     *
791     * @param sourceColumn the result column includes this column name
792     */
793    public void setSourceColumn(TResultColumn sourceColumn) {
794        this.sourceColumn = sourceColumn;
795        this.setDbObjectTypeDirectly(EDbObjectType.column);
796    }
797
798    /**
799     * Set the source column without changing dbObjectType.
800     * Used by resolver2 for legacy API compatibility when the column
801     * is already properly typed (e.g., star-inferred columns).
802     *
803     * @param sourceColumn the result column includes this column name
804     */
805    public void setSourceColumnOnly(TResultColumn sourceColumn) {
806        this.sourceColumn = sourceColumn;
807    }
808
809    /**
810     * The result column which include this column
811     * <pre>
812     *     select salary + 1000 from emp
813     * </pre>
814     * In the above SQL, <code>salary + 1000</code> is the result column while <code>salary</code> is this column name.
815     *
816     * @return the result column includes this column name
817     */
818    public TResultColumn getSourceColumn() {
819
820        return sourceColumn;
821    }
822
823    /**
824     * The <b>immediate</b> source table where this column is visible in the current scope.
825     *
826     * <p>This represents the table/subquery/CTE that directly exposes this column in the FROM clause,
827     * NOT the final physical table after tracing through subqueries or CTEs.</p>
828     *
829     * <h3>Semantic Difference: sourceTable vs finalTable</h3>
830     * <ul>
831     *   <li><b>sourceTable</b> (this field): The immediate/direct source in the current scope.
832     *       For a column from a subquery, this points to the subquery's TTable.</li>
833     *   <li><b>finalTable</b> (via {@code getResolution().getColumnSource().getFinalTable()}):
834     *       The final physical table after tracing through all subqueries and CTEs.</li>
835     * </ul>
836     *
837     * <h3>Example</h3>
838     * <pre>{@code
839     * SELECT title FROM (SELECT * FROM books) sub
840     *
841     * For the 'title' column in outer SELECT:
842     * - sourceTable = TTable for subquery 'sub' (tableType=subquery)
843     * - finalTable  = TTable for 'books' (the physical table)
844     * }</pre>
845     *
846     * @see #getSourceTable()
847     * @see gudusoft.gsqlparser.resolver2.model.ColumnSource#getFinalTable()
848     */
849    private TTable sourceTable;
850
851    /**
852     * This column must be in this syntax: table.column, otherwise, this method always return false.
853     * Match tableToken with the input pTable, compare the alias of pTable to tableToken at first,
854     * If not the same, then compare the table name directly. This method can handle quoted name correctly.
855     *
856     * This method is used by parser internally.
857     *
858     * @param pTable table used to match {@link #getTableToken()} of this column object
859     * @return true if input table is matched with tableToken of this column object
860     */
861    public boolean resolveWithThisTable(TTable pTable){
862        boolean lcResult = false;
863        if (getTableString().length() == 0) return false;
864        if (pTable.getAliasName().length() > 0) {
865            lcResult = pTable.checkTableByName(getTableString().toString()); //pTable.getAliasName().toString().equalsIgnoreCase(getTableString().toString());
866            if ((!lcResult)&&(getSchemaString().length()>0)&&(this.databaseToken == null)){
867                // Only apply alias-to-schema matching for 3-part names (alias.column.field).
868                // For 4-part names (db.schema.table.column), databaseToken is set and the
869                // schema position IS the schema, not an alias. (Mantis #4268)
870                lcResult = pTable.getAliasName().toString().equalsIgnoreCase(getSchemaString().toString());
871                if (lcResult){
872                    // table.column.field, table was recognized as schema in the parser, change the part token to property token
873                    this.columnToProperty();
874                }
875            }
876        }
877        if (lcResult) return true;
878
879        if (((pTable.isBaseTable()||(pTable.isCTEName()))&&(getTableToken() != null)&&(pTable.getTableName().getTableToken() != null))) {
880            // lcResult = getTableToken().toUnQuotedString().equalsIgnoreCase(pTable.getTableName().getTableToken().toUnQuotedString());
881            String s1 = TBaseType.getTextWithoutQuoted(getTableToken().toString());
882            String s2 = TBaseType.getTextWithoutQuoted(pTable.getTableName().getTableToken().toString());
883//            System.out.println("table1: "+s1);
884//            System.out.println("table1: "+s2);
885            lcResult = s1.equalsIgnoreCase(s2);
886
887            if (lcResult && (!pTable.getPrefixDatabase().isEmpty()) && (!this.getDatabaseString().isEmpty()) && (!pTable.getPrefixDatabase().equalsIgnoreCase(this.getDatabaseString().toString()))) {
888                // teradata: UPDATE foodmart.STRTOK_TIME A SET SYSTEM_DESK = testdatabase.STRTOK_TIME.SYSTEM_DESK
889                // table STRTOK_TIME in testdatabase should be treat as the same one in foodmart
890                lcResult = false;
891            }
892
893
894            if ((!lcResult)&&(getSchemaString().length()>0)&&(this.databaseToken == null)){
895                // Only apply table-name-to-schema matching for 3-part names (table.column.field).
896                // For 4-part names (db.schema.table.column), databaseToken is set and the
897                // schema position IS the schema, not a table name. (Mantis #4268)
898               // lcResult = pTable.getTableName().getTableToken().toUnQuotedString().equalsIgnoreCase(getSchemaString().toString());
899                lcResult = TBaseType.getTextWithoutQuoted(pTable.getTableName().getTableToken().toString()).equalsIgnoreCase(getSchemaString().toString());
900                if (lcResult){
901                    // table.column.field, table was recognized as schema in the parser, change the part token to property token
902                    this.columnToProperty();
903                }
904            }
905        }
906        return lcResult;
907    }
908
909    /**
910     * Check whether a column is prefixed by a table like this: <code>table.column</code>
911     *
912     * @return true if this column is in syntax like this: <code>table.column</code>
913     */
914    public boolean isQualified(){
915        return (getTableString().length() > 0);
916    }
917
918    public int getValidate_column_status() {
919        return validate_column_status;
920    }
921
922    public void setValidate_column_status(int validate_column_status) {
923        this.validate_column_status = validate_column_status;
924    }
925
926    private int validate_column_status = TBaseType.CAN_BE_COLUMN_NOT_VALIDATE_YET;
927    /**
928     * Check whether a column name is syntax valid in a specific database vendor.
929     * For example, in Oracle, <code>rowid</code> is not a valid column name.
930     *
931     * @param pDBVendor in which the database vendor the syntax of this column is checked
932     * @return true if this objectName can be used as a column name in the specified database
933     */
934    public boolean isValidColumnName(EDbVendor pDBVendor){
935       boolean lcResult = true;
936       if (validate_column_status == TBaseType.VALIDATED_CAN_BE_A_COLUMN_NAME) return true;
937       if (validate_column_status == TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME) return false;
938       if (validate_column_status == TBaseType.MARKED_NOT_A_COLUMN_IN_COLUMN_RESOLVER) return false;
939       if (validate_column_status == TBaseType.COLUMN_LINKED_TO_COLUMN_ALIAS_IN_OLD_ALGORITHM) return false;
940
941
942
943       if ((getObjectType() == TObjectName.ttobjVariable)
944               ||(getDbObjectType() == EDbObjectType.variable)
945               ||(getObjectType() == TObjectName.ttobjColumnAlias)
946               || (getDbObjectType() == EDbObjectType.xmlElement)
947               || (getDbObjectType() == EDbObjectType.date_time_part)
948               || (getDbObjectType() == EDbObjectType.constant)
949               || (getDbObjectType() == EDbObjectType.function)
950       ) {
951            validate_column_status = TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME;
952            return false;
953        }
954
955        // Numeric literals (e.g., "5" in LIMIT 5, "10" in TOP 10) are never valid column names.
956        // Some grammars wrap Number tokens in createObjectName() which causes them to flow through
957        // column linking. The lexer may assign ttkeyword or ttnumber as the token type depending
958        // on the vendor, so we check both the token type and the actual text content.
959        // This check is vendor-agnostic since no SQL dialect allows unquoted numeric literals
960        // as column names.
961        if (getPartToken() != null) {
962            TSourceToken pt = getPartToken();
963            if (pt.tokentype == ETokenType.ttnumber) {
964                validate_column_status = TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME;
965                return false;
966            }
967            // Some lexers (e.g., Hive) assign ttkeyword to Number tokens.
968            // Check if the text is a numeric literal (integer or decimal).
969            String tokenText = pt.toString();
970            if (tokenText.length() > 0 && isNumericLiteral(tokenText)) {
971                validate_column_status = TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME;
972                return false;
973            }
974        }
975
976        if (pDBVendor == EDbVendor.dbvsybase){
977            TSourceToken pt = getPartToken();
978            if ( pt != null){
979                if (pt.tokentype == ETokenType.ttdqstring){
980                    //"0123", quoted string start with a number can't a column
981                    if ((pt.toString().charAt(1) >= '0')
982                            &&(pt.toString().charAt(1) <= '9')){
983                        lcResult = false;
984                    }else if (pt.toString().length() == 2){
985                        //"", empty
986                        lcResult = false;
987                    }else if (pt.toString().substring(1,pt.toString().length()-1).trim().length() == 0){
988                        //"  "
989                        lcResult = false;
990                    }
991                }
992            }
993        }
994
995        if (getPartToken() != null){
996            if (getPartToken().tokentype == ETokenType.ttkeyword){
997
998                switch (pDBVendor){
999                    case dbvmssql:
1000                        //lcResult = this.getGsqlparser().getFlexer().canBeColumnName(getPartToken().tokencode);
1001                        lcResult = TLexerMssql.canBeColumnName(getPartToken().tokencode);
1002                        break;
1003                    case dbvsybase:
1004                        lcResult = !keywordChecker.isKeyword(getPartToken().toString(), EDbVendor.dbvsybase, "15.7", true);
1005                        break;
1006                    default:
1007                        break;
1008                }
1009            }
1010        }
1011
1012        if ((toString().startsWith("@"))||((toString().startsWith(":"))&&(toString().indexOf(".")==-1)))
1013        {
1014            setObjectType(TObjectName.ttobjNotAObject);
1015            lcResult = false;
1016        }
1017
1018        switch (pDBVendor){
1019            case dbvoracle:
1020                if ( //(getColumnNameOnly().compareToIgnoreCase ("rowid") == 0)||
1021                         (getColumnNameOnly().compareToIgnoreCase ("sysdate") == 0)
1022                        || (getColumnNameOnly().compareToIgnoreCase ("nextval") == 0)
1023                        || (getColumnNameOnly().compareToIgnoreCase ("rownum") == 0)
1024                        || (getColumnNameOnly().compareToIgnoreCase ("level") == 0)
1025                        || (getColumnNameOnly().compareToIgnoreCase ("user") == 0)
1026                ){
1027                    setObjectType(TObjectName.ttobjNotAObject);
1028                    lcResult = false;
1029                }
1030                if ((toString().startsWith(":"))&&(toString().indexOf(".") == -1))
1031                { // :bindv, but :new.column should not be enter here
1032                    setObjectType(TObjectName.ttobjNotAObject);
1033                    lcResult = false;
1034                }
1035                break;
1036            case dbvmssql:
1037                if ((getColumnNameOnly().compareToIgnoreCase ("system_user") == 0)
1038                ){
1039                    //setObjectType(TObjectName.ttobjNotAObject);
1040                    lcResult = false;
1041                }
1042                break;
1043            case dbvmysql:
1044                if (toString().startsWith("\"")){
1045                    // "X" is a string literal
1046                    lcResult = false;
1047                }
1048                if (keywordChecker.isKeyword(toString(),EDbVendor.dbvmysql,"6.0",true)){
1049                    isReservedKeyword = true;
1050                    lcResult = false;
1051                }
1052                break;
1053            case dbvteradata:
1054                if ((getObjectString().length() == 0)&&((getColumnNameOnly().compareToIgnoreCase ("account") == 0)
1055                        ||(getColumnNameOnly().compareToIgnoreCase ("current_date") == 0)
1056                        ||(getColumnNameOnly().compareToIgnoreCase ("current_role") == 0)
1057                        ||(getColumnNameOnly().compareToIgnoreCase ("current_time") == 0)
1058                        ||(getColumnNameOnly().compareToIgnoreCase ("current_timestamp") == 0)
1059                        ||(getColumnNameOnly().compareToIgnoreCase ("current_user") == 0)
1060                        ||(getColumnNameOnly().compareToIgnoreCase ("database") == 0)
1061                        ||((getColumnNameOnly().compareToIgnoreCase ("date") == 0)&&( this.getDbObjectType() != EDbObjectType.column ))
1062                        ||(getColumnNameOnly().compareToIgnoreCase ("profile") == 0)
1063                        ||(getColumnNameOnly().compareToIgnoreCase ("role") == 0)
1064                        ||(getColumnNameOnly().compareToIgnoreCase ("session") == 0)
1065                        ||(getColumnNameOnly().compareToIgnoreCase ("time") == 0)
1066                        ||(getColumnNameOnly().compareToIgnoreCase ("user") == 0)
1067                        ||(getColumnNameOnly().compareToIgnoreCase ("sysdate") == 0)
1068                )){
1069                    lcResult = false;
1070                }
1071                break;
1072            case dbvpostgresql:
1073                if (toString().startsWith("$")){
1074                    if ((toString().charAt(1) >= '0')
1075                            &&(toString().charAt(1) <= '9')){
1076                        this.setDbObjectType(EDbObjectType.variable);
1077                        lcResult = false;
1078                    }
1079                }
1080                break;
1081            case dbvbigquery:
1082                if ((getColumnNameOnly().compareToIgnoreCase ("CURRENT_DATE") == 0)
1083                         ||(getColumnNameOnly().compareToIgnoreCase ("CURRENT_TIME") == 0)
1084                        ||(getColumnNameOnly().compareToIgnoreCase ("CURRENT_TIMESTAMP") == 0)
1085                ){
1086                    //setObjectType(TObjectName.ttobjNotAObject);
1087                    lcResult = false;
1088                }
1089                break;
1090        }
1091
1092       if(lcResult){
1093           validate_column_status = TBaseType.VALIDATED_CAN_BE_A_COLUMN_NAME;
1094       }else{
1095           validate_column_status = TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME;
1096       }
1097
1098       return lcResult;
1099
1100    }
1101
1102    /**
1103     * Check if a string represents a numeric literal (integer or decimal).
1104     * Examples: "5", "10", "3.14", ".5"
1105     */
1106    private static boolean isNumericLiteral(String text) {
1107        if (text == null || text.isEmpty()) return false;
1108        boolean hasDigit = false;
1109        boolean hasDot = false;
1110        for (int i = 0; i < text.length(); i++) {
1111            char c = text.charAt(i);
1112            if (c >= '0' && c <= '9') {
1113                hasDigit = true;
1114            } else if (c == '.' && !hasDot) {
1115                hasDot = true;
1116            } else {
1117                return false;
1118            }
1119        }
1120        return hasDigit;
1121    }
1122
1123    private boolean expandStarColumns = false;
1124    ArrayList<String> expandedStarColumns = new ArrayList<>();
1125
1126    public ArrayList<String> getColumnsLinkedToStarColumn() {
1127        if (expandStarColumns) return expandedStarColumns;
1128
1129        //TTable sourceTable = this.getSourceTable();
1130        if (getSourceTableList().size() > 0){
1131            for( int i = 0;i < getSourceTableList().size();i++){
1132                for(String c:getSourceTableList().get(i).getExpandedStarColumns()){
1133                    expandedStarColumns.add(c);
1134                }
1135            }
1136        }
1137
1138        expandStarColumns = true;
1139        return expandedStarColumns;
1140
1141    }
1142
1143    private ArrayList<String> columnsLinkedToStarColumn = new ArrayList<String>();
1144
1145    private TResultColumnList columnsLinkedToStar;
1146
1147    public void setColumnsLinkedToStar(TResultColumnList columnsLinkedToStar) {
1148        this.columnsLinkedToStar = columnsLinkedToStar;
1149        for(TResultColumn rc:columnsLinkedToStar){
1150            if (rc.getColumnAlias()!= ""){
1151                columnsLinkedToStarColumn.add(rc.getColumnAlias());
1152            }else{
1153                columnsLinkedToStarColumn.add(rc.getColumnNameOnly());
1154            }
1155        }
1156
1157        this.setDbObjectTypeDirectly(EDbObjectType.column);
1158    }
1159
1160    /**
1161     * if this is a star column(column name is *), and the value of this star column
1162     * is derived from a subquery, then, this field points to the select list in the subquery
1163     *
1164     * @return the select list in the subquery
1165     */
1166    public TResultColumnList getColumnsLinkedToStar() {
1167        return columnsLinkedToStar;
1168    }
1169
1170    /**
1171     * Set the table this column belongs to. Used by parser internally.
1172     *
1173     * @param sourceTable table contains this column
1174     */
1175    public void setSourceTable(TTable sourceTable) {
1176
1177        // INSERT INTO "omni"."omni_upload_t1a8067802b804755b1d29ee935b3b0bc" VALUES ($1, $2, $3, $4, $5)
1178        // if this objectname is $1, then just return
1179        if (this.getDbObjectType() == EDbObjectType.parameter) {
1180            // to avoid this parameter been checked in TAttributeResolver preVisit(TObjectName attribute)
1181            this.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_TABLE_IN_OLD_ALGORITHM);
1182            return;
1183        }
1184
1185        this.sourceTable = sourceTable;
1186        // 如果 column token 的 tokentype 为 ETokenType.ttkeyword, 那么调整为 ETokenType.ttidentifier
1187        if ((this.getPartToken() != null)&&(this.getPartToken().tokentype == ETokenType.ttkeyword)){
1188            if ((this.getPartToken().getDbObjectType() == EDbObjectType.column)||(this.getPartToken().getDbObjectType() == EDbObjectType.unknown)){
1189                this.getPartToken().tokentype = ETokenType.ttidentifier;
1190            }
1191        }
1192        this.setDbObjectTypeDirectly(EDbObjectType.column);
1193
1194        // If this column was previously an orphan and is now linked to a table,
1195        // remove it from the orphanColumns list of its owning statement
1196        if (sourceTable != null && this.isOrphanColumn() && this.getOwnStmt() != null) {
1197            TObjectNameList orphanColumns = this.getOwnStmt().getOrphanColumns();
1198            if (orphanColumns != null) {
1199                orphanColumns.removeElement(this);
1200            }
1201            this.setOrphanColumn(false);
1202        }
1203    }
1204
1205    public void setSourceTableBySQLResolver(TCustomSqlStatement sqlStatement, TAttributeNode attributeNode, TTable newSourceTable) {
1206        // 如果 column token 的 tokentype 为 ETokenType.ttkeyword, 那么调整为 ETokenType.ttidentifier
1207        if ((this.getPartToken() != null)&&(this.getPartToken().tokentype == ETokenType.ttkeyword)){
1208            if ((this.getPartToken().getDbObjectType() == EDbObjectType.column)||(this.getPartToken().getDbObjectType() == EDbObjectType.unknown)){
1209                this.getPartToken().tokentype = ETokenType.ttidentifier;
1210            }
1211        }
1212
1213        if ((this.sourceTable != null) && (this.sourceTable.equals(newSourceTable))) return;
1214
1215        if ((this.getResolveStatus() == TBaseType.RESOLVED_AND_FOUND ) 
1216                || (newSourceTable.getTableType() != ETableSource.subquery)){// 关联到 subquery 的 column 本能算真正找到 table,因此还不能去掉 orphan column
1217            if (sqlStatement.getOrphanColumns().removeElement(this)){
1218                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
1219                    TBaseType.log(String.format("Remove orphan column <%s> find in old algorithm",this.toString()),TLog.WARNING,this);
1220                }
1221                // remove the waring in sql statement's error syntax list
1222                TCustomSqlStatement currentStatement = sqlStatement;
1223                while (currentStatement != null) {
1224                    if (currentStatement.getSyntaxHints() != null) {
1225                        for(int i=0; i<currentStatement.getSyntaxHints().size(); i++) {
1226                            TSyntaxError syntaxError = currentStatement.getSyntaxHints().get(i);
1227                            if (syntaxError.errortype == EErrorType.sphint) {
1228                                if ((syntaxError.lineNo == this.getStartToken().lineNo)||(syntaxError.columnNo == this.getStartToken().columnNo)) {
1229                                    currentStatement.getSyntaxHints().remove(i);
1230                                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
1231                                        TBaseType.log(String.format("Remove orphan column <%s> warning message in old algorithm", this.toString()), TLog.WARNING, this);
1232                                    }
1233                                    break;
1234                                }
1235                            }
1236                        }
1237                    }
1238                    currentStatement = currentStatement.getParentStmt();
1239                }
1240            }
1241        }else{
1242            TBaseType.log(String.format("Found orphan column <%s> find in old algorithm in subquery %s, but NOT remove it from orphan list",this.toString(),newSourceTable.getAliasName()),TLog.WARNING,this);
1243        }
1244
1245        if (this.sourceTable != null){
1246            if (this.sourceTable.getLinkedColumns().removeElement(this)){
1247                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
1248                    TBaseType.log(String.format("Remove <%s> at addr: %s from table <%s> that found in old algorithm, new linked table is: %s"
1249                            ,this.toString(),Integer.toHexString(this.hashCode()),this.sourceTable.toString(),newSourceTable.toString())
1250                            ,TLog.WARNING,this);
1251                }
1252            }
1253        }
1254        this.sourceTable = newSourceTable;
1255        this.sourceTable.getLinkedColumns().addObjectName(this);
1256
1257        if (this.getSourceColumn() == null){
1258           // if (attributeNode.isAttributeCreatedFromAliasColumn()){
1259                this.setSourceColumn(attributeNode.getSubLevelResultColumn());
1260           // }
1261        }
1262
1263        this.setDbObjectTypeDirectly(EDbObjectType.column);
1264    }
1265
1266    /**
1267     * Get the <b>immediate</b> source table where this column is visible in the current scope.
1268     *
1269     * <p>This returns the table/subquery/CTE that directly exposes this column in the FROM clause,
1270     * NOT the final physical table after tracing through subqueries or CTEs.</p>
1271     *
1272     * <p>To get the final physical table (after tracing through all layers), use:
1273     * {@code getResolution().getColumnSource().getFinalTable()}</p>
1274     *
1275     * <h3>Example</h3>
1276     * <pre>{@code
1277     * SELECT title FROM (SELECT * FROM books) sub
1278     *
1279     * For the 'title' column in outer SELECT:
1280     * - getSourceTable()           → TTable for subquery 'sub' (tableType=subquery)
1281     * - resolution.getFinalTable() → TTable for 'books' (the physical table)
1282     * }</pre>
1283     *
1284     * @return The immediate source table, or null if not resolved
1285     * @see #sourceTable
1286     * @see gudusoft.gsqlparser.resolver2.model.ColumnSource#getFinalTable()
1287     */
1288    public TTable getSourceTable() {
1289        // If new resolver determined this column is ambiguous, don't return old Phase 1 value
1290        // This ensures ambiguous columns are treated as orphan by the formatter
1291        // EXCEPTION: Star columns (*) should preserve their sourceTable for proper output
1292        if (resolution != null && resolution.isAmbiguous()) {
1293            String colName = getColumnNameOnly();
1294            if (colName != null && colName.equals("*")) {
1295                // Star columns keep their Phase 1 sourceTable
1296                return sourceTable;
1297            }
1298            if (gudusoft.gsqlparser.TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
1299                System.out.println("[TObjectName.getSourceTable] Column '" + colName +
1300                    "' is AMBIGUOUS - returning null instead of " +
1301                    (sourceTable != null ? sourceTable.getName() : "null"));
1302            }
1303            return null;
1304        }
1305        if (resolution == null && sourceTable != null) {
1306            if (gudusoft.gsqlparser.TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
1307                System.out.println("[TObjectName.getSourceTable] Column '" + getColumnNameOnly() +
1308                    "' has NO resolution - using Phase 1 sourceTable: " + sourceTable.getName());
1309            }
1310        }
1311        return sourceTable;
1312    }
1313
1314    public void setResolveStatus(int resolveStatus) {
1315        this.resolveStatus = resolveStatus;
1316
1317        if (this.resolveStatus == TBaseType.RESOLVED_AND_FOUND){
1318            this.setDbObjectTypeDirectly(EDbObjectType.column);
1319        }
1320
1321    }
1322
1323    private int resolveStatus = TBaseType.NOT_RESOLVED_YET;
1324
1325    public int getResolveStatus() {
1326        return resolveStatus;
1327    }
1328
1329    public void   TObjectName(){
1330
1331    }
1332
1333    private TObjectNameList columnAttributes = null;
1334
1335    private boolean  subscripts;
1336    private  TIndirection indirection;
1337
1338    /**
1339     * PostgreSQL column with array types
1340     * <pre>
1341     *     CREATE TABLE sal_emp (
1342     *          name            text,
1343     *          pay_by_quarter  integer[],
1344     *          schedule        text[][]
1345     *     );
1346     * </pre>
1347     * In the above SQL, this method returns true for <code>pay_by_quarter</code> column.
1348     *
1349     * @return true if this objectName is array type
1350     */
1351    public boolean isSubscripts() {
1352        return subscripts;
1353    }
1354
1355    public void setIndirection(TIndirection indirection) {
1356        if(indirection == null) return;
1357
1358        this.indirection = indirection;
1359        // setup the exceptReplaceClause of the last indirection to the parent object which is set in the .y bnf file
1360        if (indirection.getIndices() != null){
1361            if (indirection.getIndices().getElement(indirection.getIndices().size()-1).getAttributeName() != null){
1362                setExceptReplaceClause(indirection.getIndices().getElement(indirection.getIndices().size()-1).getAttributeName().getExceptReplaceClause());
1363
1364            }
1365        }
1366
1367        // possible syntax, support in postgresql only in current version:
1368        // [ indirection ], list in [] was indirection
1369        //
1370        // tablename[.column]
1371        // tablename[.*]
1372        // $1[.somecolumn]
1373        //
1374        // mytable[.arraycolumn[4]]
1375        // mytable[.two_d_column[17][34]]
1376        // $1[[10:42]]
1377        //
1378
1379        if (this.getObjectType() == TObjectName.ttobjPositionalParameters){
1380            if(indirection.isRealIndices()){
1381                //$1[10:42]
1382                this.subscripts = true;
1383            }else{
1384                //$1.somecolumn
1385               this.setColumnTokenOfPositionalParameters(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1386            }
1387        }else{
1388            if(indirection.isRealIndices()){
1389                if (indirection.getIndices().size() == 1){
1390                    // arraycolumn[4]
1391                    this.subscripts = true;
1392                }else if (indirection.getIndices().size() >= 2){
1393                   if (!indirection.getIndices().getElement(0).isRealIndices()){
1394                        // mytable[.arraycolumn[4]]
1395                        // mytable[.two_d_column[17][34]]
1396                     //  this.setPartTokenOfIndirection(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1397                       this.subscripts = true;
1398                     //  this.indirection.getIndices().remove(0);
1399                   }
1400                }
1401            }
1402
1403            //else{
1404                // 首先查找 : 和 [ 分隔符,如果找到,在该分隔符前的是 column,如果没有找到按照一般 qualified name 规则处理
1405                // https://docs.snowflake.com/en/user-guide/querying-semistructured.html
1406                int elementIndex = -1;
1407                for(int i=0;i<indirection.getIndices().size();i++){
1408                    TIndices tmp = indirection.getIndices().getElement(i);
1409                    if ((tmp.getStartToken().tokencode == ':')||(tmp.getStartToken().tokencode == '[')||(tmp.getStartToken().tokencode == TBaseType.bind_v)){
1410                        elementIndex = i;
1411                        break;
1412                    }
1413                }
1414                if (elementIndex >= 0){
1415                    // 找到了 : 和 [ 分隔符
1416                    if (elementIndex == 0){
1417                        // snowflake, <column>:<level1_element>
1418                        // everything already perfect, nothing need to be changed
1419                        partToken.setDbObjectType(EDbObjectType.column);
1420                    }else if (elementIndex == 1){
1421                        // snowflake, table.column:<level1_element>
1422                        objectToken = partToken;
1423                        objectToken.setDbObjectType(EDbObjectType.table);
1424                        partToken = indirection.getIndices().getElement(elementIndex-1).getAttributeName().getPartToken();
1425                        partToken.setDbObjectType(EDbObjectType.column);
1426                    }else if (elementIndex == 2){
1427                        // snowflake, schema.table.column:<level1_element>
1428                        schemaToken = partToken;
1429                        schemaToken.setDbObjectType(EDbObjectType.schema);
1430                        objectToken = indirection.getIndices().getElement(elementIndex-2).getAttributeName().getPartToken();
1431                        objectToken.setDbObjectType(EDbObjectType.table);
1432                        partToken = indirection.getIndices().getElement(elementIndex-1).getAttributeName().getPartToken();
1433                        partToken.setDbObjectType(EDbObjectType.column);
1434                    }
1435                }else{
1436                    // 一般 qualified name 规则处理
1437                    if (indirection.getIndices().size() == 1){
1438                        this.setPartTokenOfIndirection(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1439                    }else if (indirection.getIndices().size() == 2){
1440                        schemaToken = partToken;
1441                        schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
1442                        objectToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1443                        objectToken.setDbObjType(ttobjTable);
1444                        partToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1445                        partToken.setDbObjType(ttobjColumn);
1446                    }else if (indirection.getIndices().size() == 3){
1447                        // db.schema.tablename.column
1448                        databaseToken = partToken;
1449                        databaseToken.setDbObjectType(EDbObjectType.database);
1450                        partToken = indirection.getIndices().getElement(2).getAttributeName().getPartToken();
1451                        partToken.setDbObjectType(EDbObjectType.column);
1452                        objectToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1453                        objectToken.setDbObjectType(EDbObjectType.table);
1454                        schemaToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1455                        schemaToken.setDbObjectType(EDbObjectType.schema);
1456                    }
1457                }
1458
1459//                if (indirection.getIndices().size() == 1){
1460//                    if ((indirection.getIndices().getElement(0).getStartToken().tokencode == ':')
1461//                        ||(indirection.getIndices().getElement(0).getStartToken().tokencode == TBaseType.bind_v))
1462//                    {
1463//                        // snowflake, <column>:<level1_element>
1464//
1465//                    }else{
1466//                        // tablename[.column]
1467//                        // tablename[.*]
1468//                        this.setPartTokenOfIndirection(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1469//                    }
1470//                }else if (indirection.getIndices().size() == 2){
1471//                    if ((indirection.getIndices().getElement(0).getStartToken().tokencode == ':')
1472//                            ||(indirection.getIndices().getElement(0).getStartToken().tokencode == TBaseType.bind_v))
1473//                    {
1474//                        // snowflake, <column>:<level1_element>
1475//
1476//                    }else {
1477//                        // schema.tablename.column
1478//                        schemaToken = partToken;
1479//                        schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
1480//                        objectToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1481//                        objectToken.setDbObjType(ttobjTable);
1482//                        partToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1483//                        partToken.setDbObjType(ttobjColumn);
1484//                    }
1485//                }else if (indirection.getIndices().size() == 3){
1486//                    // db.schema.tablename.column
1487//                    databaseToken = partToken;
1488//                    databaseToken.setDbObjectType(EDbObjectType.database);
1489//                    partToken = indirection.getIndices().getElement(2).getAttributeName().getPartToken();
1490//                    partToken.setDbObjectType(EDbObjectType.column);
1491//                    objectToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1492//                    objectToken.setDbObjectType(EDbObjectType.table);
1493//                    schemaToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1494//                    schemaToken.setDbObjectType(EDbObjectType.schema);
1495//                }
1496
1497          //  }
1498
1499        }
1500
1501    }
1502
1503    /**
1504     * Array element of this objectName
1505     * <pre>
1506     *     select arraycolumn[4] from t;
1507     * </pre>
1508     * In the above SQL, this method returns <code>[4]</code> of this objectName.
1509     *
1510     * @return array element of this objectName
1511     * @see gudusoft.gsqlparser.nodes.TIndirection
1512     */
1513    public TIndirection getIndirection() {
1514        return indirection;
1515    }
1516
1517    private void setPartTokenOfIndirection(TSourceToken column){
1518        parseTablename();
1519        this.partToken = column;
1520        this.partToken.setDbObjType(ttobjColumn);
1521    }
1522
1523    public void setPropertyToken(TSourceToken propertyToken) {
1524        this.propertyToken = propertyToken;
1525    }
1526
1527    public TSourceToken getAtsign() {
1528        return atsign;
1529    }
1530
1531    public TSourceToken getMethodToken() {
1532
1533        return methodToken;
1534    }
1535
1536    public TSourceToken getPropertyToken() {
1537        return propertyToken;
1538    }
1539
1540    /**
1541     *  The server part of this objectName: [server.][database.][schema.]object
1542     *
1543     * @return server part of the objectName
1544     */
1545    public TSourceToken getServerToken() {
1546        return serverToken;
1547    }
1548
1549    public TSourceToken getExclamationmark() {
1550
1551        return exclamationmark;
1552    }
1553
1554    /**
1555     *
1556     * The database link part <code>remoreserver</code> in this objectName: scott.emp@remoreserver
1557     *
1558     * @return database link
1559     */
1560    public TObjectName getDblink() {
1561
1562        return dblink;
1563    }
1564
1565    /**
1566     *  The database part of this objectName: [server.][database.][schema.]object
1567     *
1568     * @return database part of the objectName
1569     */
1570    public TSourceToken getDatabaseToken() {
1571        return databaseToken;
1572    }
1573
1574
1575    private boolean tableDetermined = true;
1576
1577    public void setTableDetermined(boolean tableDetermined) {
1578        this.tableDetermined = tableDetermined;
1579    }
1580
1581
1582    /**
1583     * Sometime, a non-qualified column can't be linked to a table without additional metadata from database.
1584     * <pre>
1585     *     select name from emp, dept
1586     * </pre>
1587     * In the above SQL, the <code>name</code> column can't be determined which table it belongs to.
1588     *
1589     * Below is a more complicated SQL that shows the relationship between column and table.
1590     * <pre>
1591     *   select
1592     *          s2.s2t1a1,
1593     *          s3.s3t1a1
1594     *   from
1595     *       (
1596     *         select *
1597     *           from subselect2table1 s2t1
1598     *       ) s2,
1599     *       (
1600     *          select *
1601     *             from  subselect3table1, subselect3table2
1602     *       ) s3
1603     * </pre>
1604     *
1605     * column s2t1a1 was linked to subselect2table1, {@link #isTableDetermined()} returns true for this column.
1606     * <br> column s3t1a1 was linked to both subselect3table1 and subselect3table2
1607     * due to lack of meta information from database, {@link #isTableDetermined()} returns false for this column.
1608     * <p>
1609     * Provide database metadata will help GSP links the column to the table correctly.
1610     *
1611     * @return true if this column can be linked to a table without doubt.
1612     * @see gudusoft.gsqlparser.TGSqlParser#setMetaDatabase
1613     */
1614    public boolean isTableDetermined() {
1615        return tableDetermined;
1616    }
1617
1618    /**
1619     * used in Oracle and teradata SQL syntax
1620     * <p>teradata:
1621     * <p>column.attribute()
1622     * <p>column.attribute().attribute() 
1623     * @param attributes
1624     */
1625    public void attributesToPropertyToken(TObjectNameList attributes){
1626        if (attributes.size() == 1){
1627            this.propertyToken = attributes.getObjectName(0).getPartToken();
1628        }
1629    }
1630
1631    public void setColumnAttributes(TObjectNameList columnAttributes) {
1632        this.columnAttributes = columnAttributes;
1633    }
1634
1635    /**
1636     * The data type of this column is structured UDT, this method returns the column's attributes.
1637     * Below is the sample SQL from <a href="https://info.teradata.com/HTMLPubs/DB_TTU_16_00/index.html#page/SQL_Reference/B035-1146-160K/fyj1472240813334.html">teradata</a>.
1638     * <pre>
1639     *
1640     *          CREATE TYPE school_record AS (
1641     *           school_name VARCHAR(20),
1642     *           GPA         FLOAT);
1643     *
1644     *          CREATE TYPE college_record AS (
1645     *           school school_record,
1646     *           major  VARCHAR(20),
1647     *           minor  VARCHAR(20));
1648     *
1649     *          CREATE TABLE student_record (
1650     *           student_id  INTEGER,
1651     *           Last_name   VARCHAR(20),
1652     *           First_name  VARCHAR(20),
1653     *           high_school school_record,
1654     *           college     college_record);
1655     *
1656     *          SELECT student_id, last_name, first_name,
1657     *           high_school.school_name(), high_school.GPA(),
1658     *           college.school().school_name(), college.school().GPA(),
1659     *           college.major(), college.minor()
1660     *          FROM student_record;
1661     *
1662     *          SELECT *.ALL FROM student_record;
1663     *          SELECT student_record.*.ALL;
1664     * </pre>
1665     * Take this column <code>college.school().school_name()</code> for example, the partToken of this objectName
1666     * should be <code>college</code>, and the value returned by this method should be
1667     * <code>school().school_name()</code>
1668     * <p>
1669     * PLEASE NOTE THAT CURRENT VERSION CAN'T HANDLE THE ABOVE SQL CORRECTLY.
1670     *
1671     * @return attributes of this structured UDT column
1672     */
1673    public TObjectNameList getColumnAttributes() {
1674        return columnAttributes;
1675    }
1676
1677    /**
1678     * Used internally.
1679     * @deprecated use {@link #setDbObjectType} instead
1680     *
1681     * @param objectType object type of this objectName
1682     */
1683    public void setObjectType(int objectType) {
1684        if (this.objectType == objectType) return;
1685        this.objectType = objectType;
1686        // set this object type to source token
1687        switch(this.getObjectType()){
1688            case TObjectName.ttobjTable:
1689           // case TObjectName.ttobjTableTemp:
1690            //    case TObjectName.ttobjTableVar:
1691                this.parseTablename();
1692                this.objectToken.setDbObjType(this.objectType);
1693                if (dbObjectType != EDbObjectType.stage){ // not already set to stage
1694                    dbObjectType = EDbObjectType.table;
1695                }
1696
1697                if ((!TSQLEnv.supportSchema(this.dbvendor))&&(this.schemaToken != null)){
1698                    this.databaseToken = this.schemaToken;
1699                    this.schemaToken = null;
1700                }
1701                break;
1702//            case TObjectName.ttobjTableCTE:
1703//                this.parseTablename();
1704//                this.objectToken.setDbObjType(this.objectType);
1705//                dbObjectType = EDbObjectType.cte;
1706//                break;
1707//            case ttObjLibrary:
1708//                this.parseTablename();
1709//                this.objectToken.setDbObjType(this.objectType);
1710//                dbObjectType = EDbObjectType.library;
1711//                break;
1712            case TObjectName.ttobjColumn:
1713                this.partToken.setDbObjType(this.objectType);
1714                dbObjectType = EDbObjectType.column;
1715                break;
1716            case TObjectName.ttobjColumnAlias:
1717                if ((this.objectToken == null) && (this.partToken != null)){
1718                   this.parseObjectName();
1719                }
1720                this.objectToken.setDbObjType(this.objectType);
1721                dbObjectType = EDbObjectType.column_alias;
1722                break;
1723//            case TObjectName.ttObjTableAlias:
1724//                this.parseObjectName();
1725//                this.objectToken.setDbObjType(this.objectType);
1726//                dbObjectType = EDbObjectType.table_alias;
1727//                break;
1728            case TObjectName.ttobjParameter:
1729                this.parseObjectName();
1730                this.objectToken.setDbObjType(this.objectType);
1731                dbObjectType = EDbObjectType.parameter;
1732                break;
1733            case TObjectName.ttobjVariable:
1734                this.parseVariableName();
1735                this.objectToken.setDbObjType(this.objectType);
1736                dbObjectType = EDbObjectType.variable;
1737                break;
1738            case TObjectName.ttobjColumnMethod:
1739                if (dbObjectType != EDbObjectType.method){
1740                    this.parseColumnMethodName();
1741                    this.partToken.setDbObjType(this.ttobjColumn);
1742                    this.methodToken.setDbObjType(this.ttobjColumnMethod);
1743                    dbObjectType = EDbObjectType.method;
1744                }
1745                break;
1746//            case TObjectName.ttobjProcedureName:
1747//                this.parseFunctionName();
1748//                this.objectToken.setDbObjType(this.objectType);
1749//                dbObjectType = EDbObjectType.procedure;
1750//                break;
1751            case TObjectName.ttobjFunctionName:
1752                this.parseFunctionName();
1753                this.objectToken.setDbObjType(this.objectType);
1754                dbObjectType = EDbObjectType.function;
1755                break;
1756//            case TObjectName.ttobjLabelName:
1757//                this.parseObjectName();
1758//                this.objectToken.setDbObjType(this.objectType);
1759//                dbObjectType = EDbObjectType.label;
1760//                break;
1761//            case TObjectName.ttobjIndexName:
1762//                this.parseObjectName();
1763//                this.objectToken.setDbObjType(this.objectType);
1764//                dbObjectType = EDbObjectType.index;
1765//                break;
1766//            case TObjectName.ttobjMaterializedViewName:
1767//                this.parseObjectName();
1768//                this.objectToken.setDbObjType(this.objectType);
1769//                dbObjectType = EDbObjectType.materializedView;
1770//                break;
1771//            case TObjectName.ttobjViewName:
1772//                this.parseObjectName();
1773//                this.objectToken.setDbObjType(this.objectType);
1774//                dbObjectType = EDbObjectType.view;
1775//                break;
1776//            case TObjectName.ttobjCursorName:
1777//                this.parseObjectName();
1778//                this.objectToken.setDbObjType(this.objectType);
1779//                dbObjectType = EDbObjectType.cursor;
1780//                break;
1781            case TObjectName.ttobjConstraintName:
1782                this.parseObjectName();
1783                this.objectToken.setDbObjType(this.objectType);
1784                dbObjectType = EDbObjectType.constraint;
1785                break;
1786//            case TObjectName.ttobjPropertyName:
1787//                this.propertyToken.setDbObjType(this.objectType);
1788//                dbObjectType = EDbObjectType.property;
1789//                break;
1790//            case TObjectName.ttobjTransactionName:
1791//                this.parseObjectName();
1792//                this.objectToken.setDbObjType(this.objectType);
1793//                dbObjectType = EDbObjectType.transaction;
1794//                break;
1795//            case TObjectName.ttobjDatabaseName:
1796//                this.parseObjectName();
1797//                this.objectToken.setDbObjType(this.objectType);
1798//                dbObjectType = EDbObjectType.database;
1799//                break;
1800            case TObjectName.ttobjStringConstant:
1801                this.parseObjectName();
1802                this.objectToken.setDbObjType(this.objectType);
1803                break;
1804//            case TObjectName.ttobjAliasName:
1805//                this.parseObjectName();
1806//                this.objectToken.setDbObjType(this.objectType);
1807//                dbObjectType = EDbObjectType.alias;
1808//                break;
1809            case TObjectName.ttobjAttribute:
1810                this.partToken.setDbObjType(this.objectType);
1811                dbObjectType = EDbObjectType.attribute;
1812                break;
1813
1814            case TObjectName.ttobjPositionalParameters:
1815                dbObjectType = EDbObjectType.parameter;
1816                break;
1817//           case TObjectName.ttobjTypeName:
1818//               this.parseObjectName();
1819//               this.objectToken.setDbObjType(this.objectType);
1820//               dbObjectType = EDbObjectType.user_defined_type;
1821//               break;
1822//            case TObjectName.ttobjPackage:
1823//                this.parseObjectName();
1824//                this.objectToken.setDbObjType(this.objectType);
1825//                dbObjectType = EDbObjectType.plsql_package;
1826//                break;
1827//            case TObjectName.ttobjSequence:
1828//                this.parseObjectName();
1829//                this.objectToken.setDbObjType(this.objectType);
1830//                dbObjectType = EDbObjectType.sequence;
1831//                break;
1832//            case TObjectName.ttobjTrigger:
1833//                this.parseObjectName();
1834//                this.objectToken.setDbObjType(this.objectType);
1835//                dbObjectType = EDbObjectType.trigger;
1836//                break;
1837            default:
1838                break;
1839        }
1840    }
1841
1842    public void setDbObjectType(EDbVendor dbVendor, EDbObjectType dbObjectType) {
1843        this.dbvendor = dbVendor;
1844        this.setDbObjectType(dbObjectType);
1845    }
1846
1847    public void setDbObjectTypeDirectly(EDbObjectType dbObjectType) {
1848        this.dbObjectType = dbObjectType;
1849    }
1850    /**
1851     * Set object type of this objectName
1852     *
1853     * @param dbObjectType database object type
1854     */
1855    public void setDbObjectType(EDbObjectType dbObjectType) {
1856        if (this.dbObjectType == dbObjectType) return;
1857        if (this.dbObjectType == EDbObjectType.stage) return;
1858        // TODO, 如果已经被设定为某个对象类型,不应该再次设置,但如果下面的语句执行,会导致部分测试用例失败,需要查具体原因
1859        // if (this.dbObjectType != EDbObjectType.unknown) return;
1860
1861        EDbObjectType prev = this.dbObjectType;
1862        this.dbObjectType = dbObjectType;
1863        if (prev == EDbObjectType.unknown){
1864            switch (dbObjectType){
1865                case column:
1866                    parseColumnName();
1867                    break;
1868                case table:
1869                case index:
1870                case synonym:
1871                case macro:
1872                case view:
1873                case stage:
1874                case task:
1875                case stream:
1876                case TEMP_TABLE:
1877                case pipe:
1878                case security_policy:
1879
1880                case plsql_package:
1881                case trigger:
1882                case transaction:
1883                case user_defined_type:
1884                case property:
1885                case cursor:
1886                case label:
1887                case table_alias:
1888                case partitionScheme:
1889                    parseTablename();
1890                    break;
1891                case library:
1892                    parseTablename();
1893                    break;
1894                case function:
1895                    parseFunctionName();
1896                    break;
1897                case procedure:
1898                case materializedView:
1899                    parseFunctionName();
1900                    break;
1901                case alias:
1902                case module:
1903                case sequence:
1904                    parseTablename();
1905                    break;
1906                case database:
1907                    this.objectToken = this.partToken;
1908                    this.databaseToken = null;
1909                    this.partToken = null;
1910                    break;
1911                case variable:
1912                    parseVariableName();
1913                    break;
1914                case schema:
1915                    this.databaseToken = this.objectToken;
1916                    this.objectToken = this.partToken;
1917                    this.schemaToken = this.partToken;
1918                    break;
1919                case method:
1920                    this.parseColumnMethodName();
1921                    this.partToken.setDbObjType(this.ttobjColumn);
1922                    //this.methodToken.setDbObjType(this.ttobjColumnMethod);
1923                    this.methodToken.setDbObjectType(EDbObjectType.method);
1924                    break;
1925                case cte:
1926                    parseObjectName();
1927                    break;
1928                case hint: //sql server hint like nolock
1929                    parseObjectName();
1930                    break;
1931                default:
1932                    break;
1933            }
1934        }
1935    }
1936
1937    private EDbObjectType dbObjectType = EDbObjectType.unknown;
1938
1939    /**
1940     * The database object type of this objectName such as table, view, column for example.
1941     * If object type is {@link gudusoft.gsqlparser.EDbObjectType#column}, {@link #getPartToken} represents
1942     * the column name, for all other object type, the name of this database object is stored in {@link #getObjectToken}
1943     *
1944     * @return database object type
1945     */
1946    public EDbObjectType getDbObjectType() {
1947        return dbObjectType;
1948    }
1949
1950    /**
1951     * @deprecated use {@link #getDbObjectType()} instead.
1952     *
1953     * @return the type of database object or variable this objectName represents for.
1954     */
1955    public int getObjectType() {
1956
1957        return objectType;
1958    }
1959
1960
1961    public void setAtsign(TSourceToken atsign) {
1962        this.atsign = atsign;
1963    }
1964
1965    public void setDblink(TObjectName dblink) {
1966        dblink.setDbObjectType(EDbObjectType.dblink);
1967        this.dblink = dblink;
1968    }
1969
1970    public void setDblink(TObjectName dblink, boolean linkToDB) {
1971        setDblink(dblink);
1972
1973        if (linkToDB){
1974            if (dblink.numberOfPart == 1){
1975                this.databaseToken = dblink.getPartToken();
1976            }
1977        }
1978    }
1979
1980    private TSourceToken serverToken = null; //sql server
1981    private TSourceToken databaseToken = null; //sql server
1982    // schemaToken.objectToken.partToken@dblink, schemaToken, partToken, and dblink is optional
1983    private TSourceToken schemaToken;
1984    private TSourceToken objectToken;
1985
1986    /*
1987     * part is a part of the object. This identifier lets you refer to a part of a schema object,
1988     * such as a column or a partition of a table. Not all types of objects have parts.
1989     */
1990    private TSourceToken partToken;
1991    private TSourceToken propertyToken = null;
1992    private TSourceToken methodToken = null;
1993    private TSourceToken atsign; //@
1994    private TObjectName dblink;
1995
1996    // Additional parts for deeply nested struct field access (BigQuery, etc.)
1997    // Stores parts beyond the 6 standard tokens (server, database, schema, object, part, property)
1998    private java.util.List<TSourceToken> additionalParts = null;
1999
2000    // ===== Phase 5: Normalized identifier cache (transient, not serialized) =====
2001    // These caches reduce repeated normalize() calls for the same TObjectName
2002    private transient String normalizedServer;
2003    private transient String normalizedDatabase;
2004    private transient String normalizedSchema;
2005    private transient String normalizedTable;
2006    private transient String normalizedColumn;
2007    private transient long cacheFingerprint = 0;  // Profile fingerprint for cache invalidation
2008
2009    public void setServerToken(TSourceToken serverToken) {
2010        this.serverToken = serverToken;
2011    }
2012
2013    public void setDatabaseToken(TSourceToken databaseToken, boolean implicit) {
2014        this.isImplicitDatabase = implicit;
2015        setDatabaseToken(databaseToken);
2016    }
2017
2018    public void setDatabaseToken(TSourceToken databaseToken) {
2019        this.databaseToken = databaseToken;
2020    }
2021
2022    public void setObjectToken(TSourceToken objectToken) {
2023        this.objectToken = objectToken;
2024    }
2025
2026    public void setPartToken(TSourceToken partToken) {
2027        this.partToken = partToken;
2028    }
2029
2030    public void setMethodToken(TSourceToken methodToken) {
2031        this.methodToken = methodToken;
2032    }
2033
2034    public void setSchemaToken(TSourceToken schemaToken, boolean implicit) {
2035        this.isImplicitSchema = implicit;
2036        setSchemaToken(schemaToken);
2037    }
2038
2039    public void setSchemaToken(TSourceToken schemaToken) {
2040
2041        this.schemaToken = schemaToken;
2042    }
2043
2044    public void setPackageToken(TSourceToken packageToken) {
2045        this.packageToken = packageToken;
2046    }
2047
2048    /**
2049     * Oracle package name
2050     *
2051     * @return the source token of Oracle package name.
2052     */
2053    public TSourceToken getPackageToken() {
2054        return packageToken;
2055    }
2056
2057    private TSourceToken packageToken = null;
2058
2059
2060    /**
2061     * The object part of this objectName such as table name, view name.
2062     *
2063     * @return object part of this objectName
2064     */
2065    public TSourceToken getObjectToken() {
2066        return objectToken;
2067    }
2068
2069    /**
2070     * The column name of this objectName if {@link #getDbObjectType} is {@link EDbObjectType#column}.
2071     * {@link #getColumnToken} returns the same value.
2072     *
2073     * @return the column name
2074     */
2075    public TSourceToken getPartToken() {
2076        return partToken;
2077    }
2078
2079    /**
2080     * The schema name of this objectName.
2081     *
2082     * @return schema name
2083     */
2084    public TSourceToken getSchemaToken() {
2085        return schemaToken;
2086    }
2087
2088
2089    private String schemaString;
2090    private String objectString;
2091    private String partString;
2092
2093    /**
2094     * String text of the package name.
2095     *
2096     * @return string of the package name,return null if empty.
2097     */
2098    public String getPackageString(){
2099        if (getPackageToken() != null) return  getPackageToken().toString();
2100        else return "";
2101    }
2102
2103    /**
2104     * String text of the server name
2105     *
2106     * @return string of the server name,return null if empty.
2107     */
2108    public String getServerString(){
2109        if (getServerToken() != null) return  getServerToken().toString();
2110        else return "";
2111    }
2112
2113    /**
2114     * String text of the database name
2115     *
2116     * @return string of the database name,return null if empty.
2117     */
2118    public String getDatabaseString(){
2119        if (isImplicitDatabase ) return "";
2120        else if (getDatabaseToken() != null) return getDatabaseToken().toString();
2121        else if ((this.dbObjectType == EDbObjectType.database) && (getObjectToken() != null)){
2122            return getObjectToken().toString();
2123        }
2124        else return "";
2125    }
2126
2127    /**
2128     * String text of schema name in a qualified name of a schema object.
2129     *
2130     *
2131     * @return string of schema name, return null if empty.
2132     */
2133    public String getSchemaString() {
2134        if (isImplicitSchema) return "";
2135        else if (schemaToken != null)
2136            return schemaToken.getAstext();
2137        else
2138            return "" ;
2139    }
2140
2141    private TSQLEnv sqlEnv = null;
2142
2143    public void setSqlEnv(TSQLEnv sqlEnv) {
2144        this.sqlEnv = sqlEnv;
2145    }
2146
2147
2148
2149    /**
2150     * This is the schema fetched from the SQLEnv. Not the direct qualified schema name of this object
2151     * search this table in the current default database and schema.
2152     *
2153     * If this is a qualified schema object, then return {@link #getSchemaString()}
2154     *
2155     * This method is only valid when the {@link #dbObjectType} is a schema object.
2156     *
2157     * @return schema name fetched from the SQLEnv
2158     */
2159    public String getImplictSchemaString() {
2160        String implictSchema = null;
2161        if (this.implictSchemaName != null) return this.implictSchemaName;
2162
2163        if (schemaToken != null) return schemaToken.toString();
2164        if (getSchemaString().length() > 0) return  getSchemaString();
2165
2166        if (sqlEnv == null) return null;
2167
2168        TSQLSchema s = searchImplicitSchema();
2169        if (s != null){
2170            implictSchema = s.getName();
2171        }
2172
2173        return implictSchema;
2174    }
2175
2176    private String implictDatabaseName;
2177    private String implictSchemaName;
2178
2179    public void setImplictDatabaseName(String implictDatabaseName) {
2180        this.isImplicitDatabase = true;
2181        this.implictDatabaseName = implictDatabaseName;
2182    }
2183
2184    public void setImplictSchemaName(String implictSchemaName) {
2185        this.isImplicitSchema = true;
2186        this.implictSchemaName = implictSchemaName;
2187    }
2188
2189    public String getImplictDatabaseString() {
2190        String implictDatabase = null;
2191        if (implictDatabaseName != null) return implictDatabaseName;
2192
2193        if (getDatabaseString().length() > 0) return  getDatabaseString();
2194
2195        if (sqlEnv == null) return null;
2196        TSQLSchema s = searchImplicitSchema();
2197        if (s != null){
2198            TSQLCatalog c = s.getCatalog();
2199            if (c != null){
2200                implictDatabase = c.getName();
2201            }
2202        }
2203
2204        return implictDatabase;
2205    }
2206
2207    protected TSQLSchema searchImplicitSchema(){
2208        TSQLSchema s = null;
2209        if (sqlEnv == null) return null;
2210        switch (dbObjectType){
2211            case table:
2212            case view:
2213                TSQLTable t = sqlEnv.searchTable(".."+this.getObjectString());
2214                if (t != null){
2215                    s = t.getSchema();
2216                }
2217
2218                break;
2219            case function:
2220            case procedure:
2221                TSQLFunction f = sqlEnv.searchFunction(".."+this.getObjectString());
2222                if (f != null){
2223                    s = f.getSchema();
2224                }
2225                break;
2226            default:
2227                break;
2228        }
2229
2230        return s;
2231    }
2232
2233    /**
2234     * The table name of this objectName, it's the same value as {@link #getObjectToken} if {@link #getDbObjectType}
2235     * is {@link gudusoft.gsqlparser.EDbObjectType#table}
2236     *
2237     * @return table name
2238     */
2239    public  TSourceToken getTableToken(){
2240        if (objectToken == null) return  null;
2241        else return objectToken;
2242    }
2243
2244    /**
2245     * String text of the table name
2246     *
2247     * @return string of the table name, return null if empty.
2248     */
2249    public String getTableString(){
2250        if (objectToken == null) return  "";
2251//        else if (!((dbObjectType == EDbObjectType.table)||(dbObjectType == EDbObjectType.view))){
2252//            return "";
2253//        }
2254        else return objectToken.toString();
2255    }
2256
2257    /**
2258     * String text of the object name
2259     *
2260     * @return string of the object name, return null if empty.
2261     */
2262    public String getObjectString() {
2263        if (objectToken != null)
2264            return objectToken.getAstext();
2265        else
2266            return "" ;
2267    }
2268
2269    /**
2270     * String text of the part name
2271     *
2272     * @return string of the part name, return null if empty.
2273     */
2274    public String getPartString() {
2275        if (partToken != null)
2276            return partToken.getAstext();
2277        else
2278            return "" ;
2279    }
2280
2281    // ===== Phase 5: Cached normalized getters (reduce repeated normalize() calls) =====
2282
2283    private transient gudusoft.gsqlparser.sqlenv.IdentifierService identifierService;
2284
2285    /**
2286     * Lazy initialization of IdentifierService for normalized identifier caching
2287     */
2288    private gudusoft.gsqlparser.sqlenv.IdentifierService getIdentifierService() {
2289        if (identifierService == null && sqlEnv != null) {
2290            gudusoft.gsqlparser.sqlenv.IdentifierProfile profile = gudusoft.gsqlparser.sqlenv.IdentifierProfile.forVendor(
2291                sqlEnv.getDBVendor(),
2292                gudusoft.gsqlparser.sqlenv.IdentifierProfile.VendorFlags.defaults()
2293            );
2294            identifierService = new gudusoft.gsqlparser.sqlenv.IdentifierService(profile, null);
2295        }
2296        return identifierService;
2297    }
2298
2299    /**
2300     * Get normalized database string with caching (Phase 5 optimization).
2301     *
2302     * <p>This method caches the normalized database name to avoid repeated normalize() calls.
2303     * The cache is invalidated automatically when the IdentifierProfile changes (e.g., vendor switch).
2304     *
2305     * @return normalized database name, or empty string if not available
2306     */
2307    public String getNormalizedDatabaseString() {
2308        if (sqlEnv == null) return getDatabaseString();
2309
2310        try {
2311            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2312            if (service == null) return getDatabaseString();
2313
2314            long currentFingerprint = service.getProfile().getFingerprint();
2315
2316            // Check if cache is valid (not null and fingerprint matches)
2317            if (normalizedDatabase == null || cacheFingerprint != currentFingerprint) {
2318                String raw = getDatabaseString();
2319                if (raw != null && !raw.isEmpty()) {
2320                    normalizedDatabase = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotCatalog);
2321                } else {
2322                    normalizedDatabase = "";
2323                }
2324                cacheFingerprint = currentFingerprint;
2325            }
2326
2327            return normalizedDatabase;
2328        } catch (Throwable t) {
2329            // Fallback to non-cached on any error
2330            return getDatabaseString();
2331        }
2332    }
2333
2334    /**
2335     * Get normalized schema string with caching (Phase 5 optimization).
2336     *
2337     * <p>This method caches the normalized schema name to avoid repeated normalize() calls.
2338     * The cache is invalidated automatically when the IdentifierProfile changes.
2339     *
2340     * @return normalized schema name, or empty string if not available
2341     */
2342    public String getNormalizedSchemaString() {
2343        if (sqlEnv == null) return getSchemaString();
2344
2345        try {
2346            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2347            if (service == null) return getSchemaString();
2348
2349            long currentFingerprint = service.getProfile().getFingerprint();
2350
2351            // Check if cache is valid
2352            if (normalizedSchema == null || cacheFingerprint != currentFingerprint) {
2353                String raw = getSchemaString();
2354                if (raw != null && !raw.isEmpty()) {
2355                    normalizedSchema = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotSchema);
2356                } else {
2357                    normalizedSchema = "";
2358                }
2359                cacheFingerprint = currentFingerprint;
2360            }
2361
2362            return normalizedSchema;
2363        } catch (Throwable t) {
2364            // Fallback to non-cached
2365            return getSchemaString();
2366        }
2367    }
2368
2369    /**
2370     * Get normalized table string with caching (Phase 5 optimization).
2371     *
2372     * <p>This method caches the normalized table name to avoid repeated normalize() calls.
2373     * The cache is invalidated automatically when the IdentifierProfile changes.
2374     *
2375     * @return normalized table name, or empty string if not available
2376     */
2377    public String getNormalizedTableString() {
2378        if (sqlEnv == null) return getTableString();
2379
2380        try {
2381            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2382            if (service == null) return getTableString();
2383
2384            long currentFingerprint = service.getProfile().getFingerprint();
2385
2386            // Check if cache is valid
2387            if (normalizedTable == null || cacheFingerprint != currentFingerprint) {
2388                String raw = getTableString();
2389                if (raw != null && !raw.isEmpty()) {
2390                    normalizedTable = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotTable);
2391                } else {
2392                    normalizedTable = "";
2393                }
2394                cacheFingerprint = currentFingerprint;
2395            }
2396
2397            return normalizedTable;
2398        } catch (Throwable t) {
2399            // Fallback to non-cached
2400            return getTableString();
2401        }
2402    }
2403
2404    /**
2405     * Get normalized column string with caching (Phase 5 optimization).
2406     *
2407     * <p>This method caches the normalized column name (from partToken) to avoid repeated normalize() calls.
2408     * The cache is invalidated automatically when the IdentifierProfile changes.
2409     *
2410     * @return normalized column name, or empty string if not available
2411     */
2412    public String getNormalizedColumnString() {
2413        if (sqlEnv == null) return getPartString();
2414
2415        try {
2416            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2417            if (service == null) return getPartString();
2418
2419            long currentFingerprint = service.getProfile().getFingerprint();
2420
2421            // Check if cache is valid
2422            if (normalizedColumn == null || cacheFingerprint != currentFingerprint) {
2423                String raw = getPartString();
2424                if (raw != null && !raw.isEmpty()) {
2425                    normalizedColumn = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotColumn);
2426                } else {
2427                    normalizedColumn = "";
2428                }
2429                cacheFingerprint = currentFingerprint;
2430            }
2431
2432            return normalizedColumn;
2433        } catch (Throwable t) {
2434            // Fallback to non-cached
2435            return getPartString();
2436        }
2437    }
2438
2439    /**
2440     * Get normalized server string with caching (Phase 5 optimization).
2441     *
2442     * <p>This method caches the normalized server name to avoid repeated normalize() calls.
2443     * The cache is invalidated automatically when the IdentifierProfile changes.
2444     *
2445     * @return normalized server name, or empty string if not available
2446     */
2447    public String getNormalizedServerString() {
2448        if (sqlEnv == null) return getServerString();
2449
2450        try {
2451            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2452            if (service == null) return getServerString();
2453
2454            long currentFingerprint = service.getProfile().getFingerprint();
2455
2456            // Check if cache is valid
2457            if (normalizedServer == null || cacheFingerprint != currentFingerprint) {
2458                String raw = getServerString();
2459                if (raw != null && !raw.isEmpty()) {
2460                    // Use dotUnknown for server since there's no dotServer type
2461                    normalizedServer = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotUnknown);
2462                } else {
2463                    normalizedServer = "";
2464                }
2465                cacheFingerprint = currentFingerprint;
2466            }
2467
2468            return normalizedServer;
2469        } catch (Throwable t) {
2470            // Fallback to non-cached
2471            return getServerString();
2472        }
2473    }
2474
2475
2476    public void setExclamationmark(TSourceToken exclamationmark) {
2477        this.exclamationmark = exclamationmark;
2478    }
2479
2480    private TSourceToken exclamationmark; // objectToken@!, ! is dblink
2481
2482    private Boolean isParsed = false;
2483
2484    private void parseObjectName(){
2485        parseTablename();
2486    }
2487
2488   private void parseTablename(){
2489       if ((this.dbObjectType == EDbObjectType.variable) ||(this.dbObjectType == EDbObjectType.stage))return;
2490
2491       switch (this.dbvendor){
2492           case dbvteradata:
2493           case dbvhive:
2494               if (objectToken != null){
2495                   databaseToken = objectToken;
2496                   //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
2497                   databaseToken.setDbObjectType(EDbObjectType.database);
2498               }
2499               objectToken = partToken;
2500               partToken = null;
2501
2502               break;
2503           default:
2504               if (databaseToken != null){
2505                   serverToken = databaseToken;
2506                   //serverToken.setDbObjType(TObjectName.ttobjServerName);
2507                   serverToken.setDbObjectType(EDbObjectType.server);
2508               }
2509
2510               if (schemaToken != null){
2511                   databaseToken = schemaToken;
2512                   //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
2513                   databaseToken.setDbObjectType(EDbObjectType.database);
2514               }
2515
2516               if (objectToken != null){
2517                   schemaToken = objectToken;
2518                   schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
2519               }
2520
2521               objectToken = partToken;
2522               partToken = null;
2523               break;
2524       }
2525
2526       if (objectToken != null){
2527           objectToken.setDbObjectType(this.dbObjectType);
2528       }
2529    }
2530
2531    private void parseVariableName(){
2532        if (databaseToken != null){
2533            serverToken = databaseToken;
2534            //serverToken.setDbObjType(TObjectName.ttobjServerName);
2535            serverToken.setDbObjectType(EDbObjectType.server);
2536        }
2537
2538        if (schemaToken != null){
2539            databaseToken = schemaToken;
2540           // databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
2541            databaseToken.setDbObjectType(EDbObjectType.database);
2542        }
2543
2544        if (objectToken != null){
2545            if (partToken != null){
2546                schemaToken = objectToken;
2547                schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
2548                objectToken = partToken;
2549                partToken = null;
2550            }else{
2551
2552            }
2553        }else{
2554            objectToken = partToken;
2555            partToken = null;
2556        }
2557    }
2558
2559    private String ansiSchemaName;
2560    private String ansiCatalogName;
2561
2562
2563    /**
2564     *  In this SQL: select * from part1.part2,
2565     *  In Hive, MySQL and Teradata, part1 will be treated as a database name, returned in getDatabaseString(),
2566     *  while getSchemaString() return empty string.
2567     *
2568     *  However, TObjectName.getAnsiSchemaName() will return part1, which means it's a schema name.
2569     *
2570     *  If a table name is not qualified with a schema name, but GSP detect the schema for this table in the metadata
2571     *  then, this method will return this detected schema name.
2572     *
2573     * @return schema name
2574     */
2575    public String getAnsiSchemaName(){
2576        String ret = this.getSchemaString();
2577        if ((ret.length() == 0) && ((this.getImplictSchemaString() != null) && (!this.getImplictSchemaString().equalsIgnoreCase("default")))){
2578            ret = this.getImplictSchemaString();
2579        }
2580
2581        switch (dbvendor){
2582            case dbvmysql:
2583            case dbvhive:
2584            case dbvteradata:
2585            case dbvimpala:
2586                ret = this.getDatabaseString();
2587                break;
2588        }
2589        return ret;
2590    }
2591
2592    /**
2593     *   If a table name is not qualified with a database name, but GSP detect the database  for this table in the metadata
2594     *   then, this method will return this detected database name.
2595     *
2596     * @return
2597     */
2598    public String getAnsiCatalogName(){
2599        String ret = this.getDatabaseString();
2600        if (( ret.length() == 0) && (this.getImplictDatabaseString() != null) && (!this.getImplictDatabaseString().equalsIgnoreCase("default"))){
2601            ret = this.getImplictDatabaseString();
2602        }
2603
2604        switch (dbvendor){
2605            case dbvmysql:
2606            case dbvhive:
2607            case dbvteradata:
2608            case dbvimpala:
2609                ret = "";
2610                break;
2611        }
2612
2613        return  ret;
2614    }
2615
2616    private void parseFunctionName(){
2617        this.parseTablename();
2618     }
2619
2620    private void parseColumnMethodName(){
2621       // objectType = ttobjColumnMethod;
2622
2623        methodToken = objectToken;
2624        partToken = schemaToken;
2625
2626        objectToken = null;//;
2627        schemaToken = null;
2628     }
2629
2630    private void parseColumnName(){
2631        assert(partToken != null);
2632     }
2633
2634    public TObjectName(){
2635    }
2636
2637    /**
2638     *  List the number of parts made up this objectName
2639     *
2640     * @return the number of parts that made up this objectName
2641     */
2642    public int getNumberOfPart() {
2643        return numberOfPart;
2644    }
2645
2646    private int numberOfPart = 1;
2647
2648    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType){
2649        return new TObjectName(dbVendor,dbObjectType);
2650    }
2651
2652
2653    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1){
2654        return new TObjectName(dbVendor,dbObjectType,token1);
2655    }
2656
2657    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType, String str) {
2658        String[] parts = str.split("\\.");
2659        if (parts.length == 1) {
2660            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]));
2661        } else if (parts.length == 2) {
2662            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]), new TSourceToken(parts[1]));
2663        } else if (parts.length == 3) {
2664            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]), new TSourceToken(parts[1]), new TSourceToken(parts[2]));
2665        } else if (parts.length == 4) {
2666            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]), new TSourceToken(parts[1]), new TSourceToken(parts[2]), new TSourceToken(parts[3]));
2667        }
2668        return new TObjectName(dbVendor, dbObjectType, new TSourceToken(str));
2669    }
2670
2671
2672    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1,TSourceToken token2){
2673        return new TObjectName(dbVendor,dbObjectType,token1,token2);
2674    }
2675
2676    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1,TSourceToken token2,TSourceToken token3){
2677        return new TObjectName(dbVendor,dbObjectType,token1,token2,token3);
2678    }
2679
2680    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1,TSourceToken token2,TSourceToken token3,TSourceToken token4){
2681        return new TObjectName(dbVendor,dbObjectType,token1,token2,token3,token4);
2682    }
2683
2684    /**
2685     * @deprecated As of v2.0.7.1, please use {@link #TObjectName(EDbObjectType, TSourceToken)} instead.
2686     *
2687     * Class constructor specifying object name and object type.
2688     * <p>
2689     * Use {@link gudusoft.gsqlparser.TGSqlParser#parseObjectName} to create an objectName more than 2 parts.
2690     *
2691     * @param token           name of this object
2692     * @param dbObjectType   type of this object
2693     */
2694    private TObjectName(TSourceToken token,EDbObjectType dbObjectType){
2695        this(dbObjectType,token);
2696    }
2697
2698    public void splitNameInQuotedIdentifier(){
2699        if (this.dbvendor != EDbVendor.dbvbigquery && this.dbvendor != EDbVendor.dbvsnowflake) return;
2700        if (this.objectToken == null) return;
2701        TSourceToken token = this.objectToken;
2702
2703        // For Snowflake IDENTIFIER function, the token is a single-quoted string literal
2704        // which has quoteType notQuoted but starts with single quote
2705        boolean isSnowflakeSingleQuotedString = (this.dbvendor == EDbVendor.dbvsnowflake)
2706                && token.toString().startsWith("'");
2707
2708        if (getQuoteType() == EQuoteType.notQuoted && !isSnowflakeSingleQuotedString) return;
2709//        if ((this.dbvendor != EDbVendor.dbvbigquery)
2710//                &&(getQuoteType() == EQuoteType.doubleQuote)) return;
2711
2712        String tokenStr = token.toString();
2713        char outerQuoteChar = tokenStr.charAt(0);
2714        String s = TBaseType.getTextWithoutQuoted(tokenStr);
2715        String[] a = s.split("[.]");
2716        if (a.length == 1){
2717            // this.objectToken = token;
2718        }else if (a.length == 2){
2719            String objPart = a[1];
2720            String schemaPart = a[0];
2721
2722            // For Snowflake IDENTIFIER function with single-quoted string containing double-quoted parts
2723            // e.g., IDENTIFIER('"SCHEMA"."TABLE"') -> parts already have double quotes
2724            // For unquoted parts, e.g., IDENTIFIER('schema.table') -> use parts as-is
2725            boolean isSnowflakeIdentifierFunction = (this.dbvendor == EDbVendor.dbvsnowflake) && (outerQuoteChar == '\'');
2726
2727            if (isSnowflakeIdentifierFunction) {
2728                this.objectToken = new TSourceToken(objPart);
2729                this.schemaToken = new TSourceToken(schemaPart);
2730            } else {
2731                this.objectToken = new TSourceToken(outerQuoteChar + objPart + outerQuoteChar);
2732                this.schemaToken = new TSourceToken(outerQuoteChar + schemaPart + outerQuoteChar);
2733            }
2734        }else if (a.length == 3){
2735            String objPart = a[2];
2736            String schemaPart = a[1];
2737            String dbPart = a[0];
2738
2739            boolean isSnowflakeIdentifierFunction = (this.dbvendor == EDbVendor.dbvsnowflake) && (outerQuoteChar == '\'');
2740
2741            if (isSnowflakeIdentifierFunction) {
2742                this.objectToken = new TSourceToken(objPart);
2743                this.schemaToken = new TSourceToken(schemaPart);
2744                this.databaseToken = new TSourceToken(dbPart);
2745            } else {
2746                this.objectToken = new TSourceToken(outerQuoteChar + objPart + outerQuoteChar);
2747                this.schemaToken = new TSourceToken(outerQuoteChar + schemaPart + outerQuoteChar);
2748                this.databaseToken = new TSourceToken(outerQuoteChar + dbPart + outerQuoteChar);
2749            }
2750        }
2751
2752    }
2753
2754    private TObjectName(EDbVendor dbVendor){
2755        this.dbvendor = dbVendor;
2756    }
2757
2758    private TObjectName(EDbVendor dbVendor,EDbObjectType dbObjectType){
2759        this.dbvendor = dbVendor;
2760        this.dbObjectType = dbObjectType;
2761    }
2762
2763    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token){
2764        this.dbvendor = dbVendor;
2765        numberOfPart = 1;
2766        this.setStartToken(token);
2767        this.setEndToken(token);
2768
2769        this.dbObjectType = dbObjectType;
2770        switch (dbObjectType){
2771            case column:
2772                this.partToken = token;
2773                break;
2774            case method:
2775                this.methodToken = token;
2776                break;
2777            case table:
2778            case function:
2779            case procedure:
2780            case materializedView:
2781            case alias:
2782            case module:
2783            case sequence:
2784            case collation:
2785                this.objectToken = token;
2786                splitNameInQuotedIdentifier();
2787                break;
2788            default:
2789                this.objectToken = token;
2790                break;
2791        }
2792    }
2793
2794    private void initWithOneToken(EDbObjectType dbObjectType,TSourceToken token){
2795        numberOfPart = 1;
2796        this.setStartToken(token);
2797        this.setEndToken(token);
2798
2799        this.dbObjectType = dbObjectType;
2800        switch (dbObjectType){
2801            case column:
2802                this.partToken = token;
2803                break;
2804            case method:
2805                this.methodToken = token;
2806                break;
2807            case table:
2808            case function:
2809            case procedure:
2810            case materializedView:
2811            case alias:
2812            case module:
2813            case sequence:
2814            case collation:
2815            case stage:
2816                this.objectToken = token;
2817                splitNameInQuotedIdentifier();
2818                break;
2819            case namespace:
2820                this.schemaToken = token;
2821                break;
2822            default:
2823                this.objectToken = token;
2824                break;
2825        }
2826    }
2827
2828    private void initWithTwoTokens(EDbObjectType dbObjectType,TSourceToken token2,TSourceToken token1){
2829        initWithOneToken(dbObjectType,token1);
2830        numberOfPart = 2;
2831        this.setStartToken(token2);
2832        this.setEndToken(token1);
2833
2834        switch (dbObjectType){
2835            case column:
2836                this.objectToken = token2;
2837                break;
2838            case method:
2839                this.partToken = token2;
2840                break;
2841            case table:
2842            case function:
2843            case procedure:
2844            case materializedView:
2845            case alias:
2846            case module:
2847            case sequence:
2848            case collation:
2849            case stage:
2850                this.schemaToken = token2;
2851                break;
2852            case namespace:
2853                this.databaseToken = token2;
2854                break;
2855            default:
2856                this.schemaToken = token2;
2857                break;
2858        }
2859
2860    }
2861
2862    private void initWithThreeTokens(EDbObjectType dbObjectType,TSourceToken token3,TSourceToken token2,TSourceToken token1){
2863        initWithTwoTokens(dbObjectType,token2,token1);
2864        numberOfPart = 3;
2865        this.setStartToken(token3);
2866        this.setEndToken(token1);
2867
2868        switch (dbObjectType){
2869            case column:
2870                this.schemaToken = token3;
2871                break;
2872            case method:
2873                this.objectToken = token3;
2874                break;
2875            case table:
2876            case function:
2877            case procedure:
2878            case materializedView:
2879            case alias:
2880            case module:
2881            case sequence:
2882            case collation:
2883            case stage:
2884                this.databaseToken = token3;
2885                break;
2886            default:
2887                this.databaseToken = token3;
2888                break;
2889        }
2890
2891    }
2892
2893    private void initWithFourTokens(EDbObjectType dbObjectType,TSourceToken token4,TSourceToken token3,TSourceToken token2,TSourceToken token1){
2894        initWithThreeTokens(dbObjectType,token3,token2,token1);
2895        numberOfPart = 4;
2896        this.setStartToken(token4);
2897        this.setEndToken(token1);
2898
2899        switch (dbObjectType){
2900            case column:
2901                this.databaseToken = token4;
2902                break;
2903            case method:
2904                this.schemaToken = token4;
2905                break;
2906            case table:
2907            case function:
2908            case procedure:
2909            case materializedView:
2910            case alias:
2911            case module:
2912            case sequence:
2913            case collation:
2914                this.serverToken = token4;
2915                break;
2916            default:
2917                this.serverToken = token4;
2918                break;
2919        }
2920
2921    }
2922
2923    /**
2924     * @deprecated As of v2.0.7.1, please use {@link TObjectName#createObjectName(EDbVendor, EDbObjectType, TSourceToken)} instead.
2925     * 
2926     * @param dbObjectType
2927     * @param token
2928     */
2929    private TObjectName(EDbObjectType dbObjectType,TSourceToken token){
2930        initWithOneToken(dbObjectType,token);
2931    }
2932
2933    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token2,TSourceToken token1){
2934        this(dbVendor,dbObjectType,token1);
2935        numberOfPart = 2;
2936        this.setStartToken(token2);
2937        this.setEndToken(token1);
2938
2939        switch (dbObjectType){
2940            case column:
2941                this.objectToken = token2;
2942                break;
2943            case method:
2944                this.partToken = token2;
2945                break;
2946            case table:
2947            case function:
2948            case procedure:
2949            case materializedView:
2950            case alias:
2951            case module:
2952            case sequence:
2953            case collation:
2954                if (dbVendor == EDbVendor.dbvteradata){
2955                    this.databaseToken = token2;
2956                }else{
2957                    this.schemaToken = token2;
2958                }
2959
2960                break;
2961            default:
2962                this.schemaToken = token2;
2963                break;
2964        }
2965
2966    }
2967
2968
2969    /**
2970     * @deprecated since ver 2.5.9.8
2971     *
2972     * @param dbObjectType
2973     * @param token2
2974     * @param token1
2975     */
2976    private TObjectName(EDbObjectType dbObjectType,TSourceToken token2,TSourceToken token1){
2977        this(dbObjectType,token1);
2978        numberOfPart = 2;
2979        this.setStartToken(token2);
2980        this.setEndToken(token1);
2981
2982        switch (dbObjectType){
2983            case column:
2984                this.objectToken = token2;
2985                break;
2986            case method:
2987                this.partToken = token2;
2988                break;
2989            case table:
2990            case function:
2991            case procedure:
2992            case materializedView:
2993            case alias:
2994            case module:
2995            case sequence:
2996            case collation:
2997                this.schemaToken = token2;
2998                break;
2999            default:
3000                this.schemaToken = token2;
3001                break;
3002        }
3003    }
3004    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3005        this(dbVendor,dbObjectType,token2,token1);
3006        numberOfPart = 3;
3007        this.setStartToken(token3);
3008        this.setEndToken(token1);
3009
3010        switch (dbObjectType){
3011            case column:
3012                this.schemaToken = token3;
3013                break;
3014            case method:
3015                this.objectToken = token3;
3016                break;
3017            case table:
3018            case function:
3019            case procedure:
3020            case materializedView:
3021            case alias:
3022            case module:
3023            case sequence:
3024            case collation:
3025                this.databaseToken = token3;
3026                break;
3027            default:
3028                this.databaseToken = token3;
3029                break;
3030        }
3031
3032    }
3033
3034
3035    /**
3036     * @deprecated since ver 2.5.9.8
3037     *
3038     * @param dbObjectType
3039     * @param token3
3040     * @param token2
3041     * @param token1
3042     */
3043    private TObjectName(EDbObjectType dbObjectType,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3044        this(dbObjectType,token2,token1);
3045        numberOfPart = 3;
3046        this.setStartToken(token3);
3047        this.setEndToken(token1);
3048
3049        switch (dbObjectType){
3050            case column:
3051                this.schemaToken = token3;
3052                break;
3053            case method:
3054                this.objectToken = token3;
3055                break;
3056            case table:
3057            case function:
3058            case procedure:
3059            case materializedView:
3060            case alias:
3061            case module:
3062            case sequence:
3063            case collation:
3064                this.databaseToken = token3;
3065                break;
3066            default:
3067                this.databaseToken = token3;
3068                break;
3069        }
3070    }
3071
3072    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token4,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3073        this(dbVendor,dbObjectType,token3,token2,token1);
3074        numberOfPart = 4;
3075        this.setStartToken(token4);
3076        this.setEndToken(token1);
3077
3078        switch (dbObjectType){
3079            case column:
3080                this.databaseToken = token4;
3081                break;
3082            case method:
3083                this.schemaToken = token4;
3084                break;
3085            case table:
3086            case function:
3087            case procedure:
3088            case materializedView:
3089            case alias:
3090            case module:
3091            case sequence:
3092            case collation:
3093                this.serverToken = token4;
3094                break;
3095            default:
3096                this.serverToken = token4;
3097                break;
3098        }
3099
3100    }
3101
3102    /**
3103     * @deprecated since ver 2.5.9.8
3104     *
3105     * @param dbObjectType
3106     * @param token4
3107     * @param token3
3108     * @param token2
3109     * @param token1
3110     */
3111    private TObjectName(EDbObjectType dbObjectType,TSourceToken token4,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3112        this(dbObjectType,token3,token2,token1);
3113        numberOfPart = 4;
3114        this.setStartToken(token4);
3115        this.setEndToken(token1);
3116
3117        switch (dbObjectType){
3118            case column:
3119                this.databaseToken = token4;
3120                break;
3121            case method:
3122                this.schemaToken = token4;
3123                break;
3124            case table:
3125            case function:
3126            case procedure:
3127            case materializedView:
3128            case alias:
3129            case module:
3130            case sequence:
3131            case collation:
3132                this.serverToken = token4;
3133                break;
3134            default:
3135                this.serverToken = token4;
3136                break;
3137        }
3138    }
3139
3140    /**
3141     * @deprecated As of v2.0.7.1, please use {@link #TObjectName(EDbObjectType, TSourceToken, TSourceToken)} instead.
3142     *
3143     * Class constructor specifying object, part name and object type.
3144     * Use this constructor to create a <code>table.column</code> objectName.
3145     * Use {@link gudusoft.gsqlparser.TGSqlParser#parseObjectName} to create an objectName more than 2 parts.
3146     *
3147     * @param pObjectToken    name of this object, usually it's the table name
3148     * @param pPartToken      name of the column
3149     * @param dbObjectType    type of this object, usually it's {@link gudusoft.gsqlparser.EDbObjectType#column}
3150     */
3151    private TObjectName(TSourceToken pObjectToken,TSourceToken pPartToken,EDbObjectType dbObjectType){
3152        this(dbObjectType,pObjectToken,pPartToken);
3153    }
3154
3155    public void init(Object arg1)
3156    {
3157        partToken = (TSourceToken)arg1;
3158        numberOfPart = 1;
3159        this.setStartToken(partToken);
3160        this.setEndToken(partToken);
3161    }
3162
3163    public void init(Object arg1, Object arg2)
3164    {
3165        if (arg1 instanceof EDbObjectType){
3166            initWithOneToken((EDbObjectType)arg1,(TSourceToken) arg2);
3167
3168        }else{
3169            numberOfPart = 0;
3170            objectToken = (TSourceToken)arg1;
3171            partToken = (TSourceToken)arg2;
3172            if (partToken != null) numberOfPart++;
3173            if (objectToken != null) numberOfPart++;
3174
3175
3176            if(objectToken != null){
3177                this.setStartToken(objectToken);
3178            }else{
3179                this.setStartToken(partToken);
3180            }
3181
3182            if (partToken != null){
3183                this.setEndToken(partToken);
3184            }else{
3185                this.setEndToken(objectToken);
3186            }
3187        }
3188    }
3189
3190    public void init(EDbObjectType dbObjectType, Object arg1, Object arg2, Object arg3){
3191        numberOfPart = 0;
3192        if (arg1 != null) numberOfPart++;
3193        if (arg2 != null) numberOfPart++;
3194        if (arg3 != null) numberOfPart++;
3195
3196        this.dbObjectType = dbObjectType;
3197        this.setStartToken((TSourceToken)arg1);
3198        this.setEndToken((TSourceToken)arg3);
3199        switch (this.dbObjectType){
3200            case column:
3201                schemaToken = (TSourceToken)arg1;
3202                objectToken = (TSourceToken)arg2;
3203                partToken = (TSourceToken)arg3;
3204                break;
3205            case table:
3206            case function:
3207            case procedure:
3208            case materializedView:
3209            case module:
3210            case sequence:
3211                databaseToken = (TSourceToken) arg1;
3212                schemaToken = (TSourceToken)arg2;
3213                objectToken = (TSourceToken)arg3;
3214                break;
3215            case alias:
3216                break;
3217            default:
3218                break;
3219        }
3220
3221    }
3222
3223    public void init(Object arg1, Object arg2, Object arg3)
3224    {
3225        if (arg1 instanceof EDbObjectType){
3226            initWithTwoTokens((EDbObjectType)arg1,(TSourceToken) arg2,(TSourceToken) arg3);
3227        }else{
3228            numberOfPart = 0;
3229            if (arg1 != null) numberOfPart++;
3230            if (arg2 != null) numberOfPart++;
3231            if (arg3 != null) numberOfPart++;
3232
3233            if (dbvendor == EDbVendor.dbvteradata){
3234                databaseToken = (TSourceToken) arg1;
3235                this.setStartToken(databaseToken);
3236            }else{
3237                schemaToken = (TSourceToken)arg1;
3238                this.setStartToken(schemaToken);
3239                if (schemaToken != null)
3240                   {schemaToken.setDbObjType(TObjectName.ttobjSchemaName);}
3241            }
3242
3243            objectToken = (TSourceToken)arg2;
3244            partToken = (TSourceToken)arg3;
3245            this.setEndToken(partToken);
3246        }
3247    }
3248
3249    public void init(Object arg1, Object arg2, Object arg3, Object arg4)
3250    {
3251        if (arg1 instanceof EDbObjectType){
3252            //this.dbObjectType = (EDbObjectType)arg1;
3253            //init(arg2,arg3,arg4);
3254            initWithThreeTokens((EDbObjectType)arg1,(TSourceToken)arg2,(TSourceToken)arg3,(TSourceToken)arg4);
3255        }else{
3256            numberOfPart = 0;
3257            if (arg1 != null) numberOfPart++;
3258            if (arg2 != null) numberOfPart++;
3259            if (arg3 != null) numberOfPart++;
3260            if (arg4 != null) numberOfPart++;
3261
3262            //serverToken = (TSourceToken)arg1;
3263            databaseToken = (TSourceToken)arg1;
3264            schemaToken = (TSourceToken)arg2;
3265            objectToken = (TSourceToken)arg3;
3266            partToken = (TSourceToken)arg4;
3267            this.setStartToken(databaseToken);
3268            this.setEndToken(partToken);
3269            if (databaseToken != null){
3270                //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
3271                databaseToken.setDbObjectType(EDbObjectType.database);
3272            }else {
3273            }
3274            if (schemaToken != null){
3275                schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
3276            }else {
3277            }
3278        }
3279    }
3280
3281    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5)
3282    {
3283        numberOfPart = 0;
3284        if (arg1 != null) numberOfPart++;
3285        if (arg2 != null) numberOfPart++;
3286        if (arg3 != null) numberOfPart++;
3287        if (arg4 != null) numberOfPart++;
3288        if (arg5 != null) numberOfPart++;
3289
3290        serverToken = (TSourceToken)arg1;
3291        databaseToken = (TSourceToken)arg2;
3292        schemaToken = (TSourceToken)arg3;
3293        objectToken = (TSourceToken)arg4;
3294        partToken = (TSourceToken)arg5;
3295
3296        this.setStartToken(serverToken);
3297        this.setEndToken(partToken);
3298
3299        if (serverToken != null){
3300            //serverToken.setDbObjType(TObjectName.ttobjServerName);
3301            serverToken.setDbObjectType(EDbObjectType.server);
3302        }else{
3303        }
3304        if (databaseToken != null){
3305            //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
3306            databaseToken.setDbObjectType(EDbObjectType.database);
3307        }else{
3308        }
3309        if (schemaToken != null){
3310            schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
3311        }else{
3312        }
3313    }
3314
3315    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6)
3316    {
3317        numberOfPart = 0;
3318        if (arg1 != null) numberOfPart++;
3319        if (arg2 != null) numberOfPart++;
3320        if (arg3 != null) numberOfPart++;
3321        if (arg4 != null) numberOfPart++;
3322        if (arg5 != null) numberOfPart++;
3323        if (arg6 != null) numberOfPart++;
3324
3325        serverToken = (TSourceToken)arg1;
3326        databaseToken = (TSourceToken)arg2;
3327        schemaToken = (TSourceToken)arg3;
3328        objectToken = (TSourceToken)arg4;
3329        partToken = (TSourceToken)arg5;
3330        propertyToken = (TSourceToken)arg6;
3331
3332        this.setStartToken(serverToken);
3333        this.setEndToken(propertyToken);
3334
3335        if (serverToken != null){
3336            //serverToken.setDbObjType(TObjectName.ttobjServerName);
3337            serverToken.setDbObjectType(EDbObjectType.server);
3338        }else{
3339        }
3340        if (databaseToken != null){
3341           // databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
3342            databaseToken.setDbObjectType(EDbObjectType.database);
3343        }else{
3344        }
3345        if (schemaToken != null){
3346            schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
3347        }else{
3348        }
3349    }
3350
3351    /**
3352     * Init with 7 tokens for deeply nested struct field access (e.g., BigQuery)
3353     * Pattern: a.b.c.d.e.f.g (7 parts)
3354     */
3355    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7)
3356    {
3357        // First 6 parts use standard tokens
3358        init(arg1, arg2, arg3, arg4, arg5, arg6);
3359
3360        // 7th part goes to additionalParts
3361        if (arg7 != null) {
3362            if (additionalParts == null) {
3363                additionalParts = new java.util.ArrayList<>();
3364            }
3365            additionalParts.add((TSourceToken) arg7);
3366            numberOfPart++;
3367            this.setEndToken((TSourceToken) arg7);
3368        }
3369    }
3370
3371    /**
3372     * Init with 8 tokens for deeply nested struct field access
3373     * Pattern: a.b.c.d.e.f.g.h (8 parts)
3374     */
3375    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8)
3376    {
3377        init(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
3378
3379        if (arg8 != null) {
3380            if (additionalParts == null) {
3381                additionalParts = new java.util.ArrayList<>();
3382            }
3383            additionalParts.add((TSourceToken) arg8);
3384            numberOfPart++;
3385            this.setEndToken((TSourceToken) arg8);
3386        }
3387    }
3388
3389    /**
3390     * Init with 9 tokens for deeply nested struct field access
3391     * Pattern: a.b.c.d.e.f.g.h.i (9 parts)
3392     */
3393    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9)
3394    {
3395        init(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
3396
3397        if (arg9 != null) {
3398            if (additionalParts == null) {
3399                additionalParts = new java.util.ArrayList<>();
3400            }
3401            additionalParts.add((TSourceToken) arg9);
3402            numberOfPart++;
3403            this.setEndToken((TSourceToken) arg9);
3404        }
3405    }
3406
3407    /**
3408     * Init with 10 tokens for deeply nested struct field access
3409     * Pattern: a.b.c.d.e.f.g.h.i.j (10 parts)
3410     */
3411    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, Object arg10)
3412    {
3413        init(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
3414
3415        if (arg10 != null) {
3416            if (additionalParts == null) {
3417                additionalParts = new java.util.ArrayList<>();
3418            }
3419            additionalParts.add((TSourceToken) arg10);
3420            numberOfPart++;
3421            this.setEndToken((TSourceToken) arg10);
3422        }
3423    }
3424
3425    /**
3426     * Get additional parts beyond the standard 6 tokens.
3427     * Used for deeply nested struct field access in databases like BigQuery.
3428     *
3429     * @return list of additional source tokens, or null if none
3430     */
3431    public java.util.List<TSourceToken> getAdditionalParts() {
3432        return additionalParts;
3433    }
3434
3435    /**
3436     * The column position of this objectName in the SQL
3437     *
3438     * @return column position
3439     */
3440    @Override
3441    public long getColumnNo() {
3442        long retval = -1;
3443        if (partToken != null) { retval = partToken.columnNo;}
3444        if (objectToken != null) {
3445              retval = objectToken.columnNo;
3446        }
3447        if (schemaToken != null) {
3448              retval = schemaToken.columnNo;
3449        }
3450        if (databaseToken != null) {
3451              retval = databaseToken.columnNo;
3452        }
3453        if (serverToken != null) {
3454              retval = serverToken.columnNo;
3455        }
3456        return retval;
3457    }
3458
3459    /**
3460     * The line number of this objectName in SQL
3461     *
3462     * @return the line number
3463     */
3464    @Override
3465    public long getLineNo() {
3466        long retval = -1;
3467        if (partToken != null) { retval = partToken.lineNo;}
3468        if (objectToken != null) {
3469              retval = objectToken.lineNo;
3470        }
3471        if (schemaToken != null) {
3472              retval = schemaToken.lineNo;
3473        }
3474        if (databaseToken != null) {
3475              retval = databaseToken.lineNo;
3476        }
3477        if (serverToken != null) {
3478              retval = serverToken.lineNo;
3479        }
3480        return retval;
3481    }
3482
3483   private TObjectNameList referencedObjects = null;
3484
3485    public TObjectNameList getReferencedObjects() {
3486        if (referencedObjects == null){
3487            referencedObjects = new TObjectNameList();
3488        }
3489        return referencedObjects;
3490    }
3491
3492    /**
3493     * Returns only the column name if it's prefixed with a table name
3494     *
3495     * @return only the column name if it's prefixed with a table name
3496     */
3497    public String getColumnNameOnly(){
3498
3499        if (getPartToken() == null) return "";
3500        else  return getPartToken().toString();
3501    }
3502
3503    public void accept(TParseTreeVisitor v){
3504        v.preVisit(this);
3505        v.postVisit(this);
3506    }
3507
3508    public void acceptChildren(TParseTreeVisitor v){
3509        v.preVisit(this);
3510        v.postVisit(this);
3511    }
3512
3513//    private TSourceToken sortType = null;
3514//
3515//    public void setSortType(TSourceToken sortType) {
3516//        this.sortType = sortType;
3517//    }
3518//
3519//    /**
3520//     * When this object is column in primary key(column,...), unique key(column,...) in sql server
3521//     * there maybe sort information like column asc, column desc
3522//     * this token represents for ASC, DESC if specified.
3523//     *
3524//     * @return ASC, DESC or null
3525//     */
3526//
3527//    public TSourceToken getSortType() {
3528//        return sortType;
3529//    }
3530
3531    /**
3532     * It's the same as {@link #getPartToken} if {@link #getDbObjectType} is {@link gudusoft.gsqlparser.EDbObjectType#column}
3533     *
3534     * @return source token that represents column, return null if this objectName is not type of column
3535     *
3536     */
3537    public TSourceToken getColumnToken(){
3538        TSourceToken ret = null;
3539        if (this.getObjectType() == ttobjColumn){
3540            ret = this.getPartToken();
3541        }
3542        return ret;
3543    }
3544
3545    /*
3546     *  re-arranage objectname to make it a valid name includes attribute name
3547      * used by teradata yacc file only.
3548      * valid syntax:
3549       * column.attr1().attr2().
3550       * column.attr1().attr2().attr3()
3551       * table.column.attr1().attr2().
3552       * table.column.attr1().attr2().attr3()
3553       *
3554     *   @return
3555     */
3556   public boolean isAttributeNameInObjectName(TSourceToken leftparen,TSourceToken rightparen){
3557        boolean ret = false;
3558        if ((this.partToken == null) || (this.objectToken == null)){
3559            return ret;
3560        }
3561        this.objectType = TObjectName.ttobjColumn;
3562        this.columnAttributes = new TObjectNameList();
3563        TObjectName attr1 = new TObjectName();
3564        attr1.objectType = TObjectName.ttobjAttribute;
3565        attr1.init(this.partToken);
3566        attr1.setEndToken(rightparen);
3567        this.columnAttributes.addObjectName(attr1);
3568
3569        this.partToken = this.objectToken;
3570
3571        if (this.schemaToken != null){
3572            this.objectToken = this.schemaToken;
3573        }
3574
3575        return true;
3576   }
3577
3578    /**
3579     * Used internally in hive .y file to merge two objectNames
3580     */
3581    public void mergeObjectName(TObjectName objectName){
3582        this.objectToken = this.partToken;
3583        this.partToken = objectName.getPartToken();
3584        this.setStartToken(objectToken);
3585        this.setEndToken(partToken);
3586    }
3587
3588    public void mergeObjectName(TObjectName objectName,TObjectName objectName2){
3589        this.schemaToken = this.partToken;
3590        this.objectToken = objectName.getPartToken();
3591        this.partToken = objectName2.getPartToken();
3592        this.setStartToken(schemaToken);
3593        this.setEndToken(partToken);
3594    }
3595
3596
3597    public void columnToProperty(){
3598        // if (numberOfPart == 1) return;
3599        if (this.pseudoTableType != EPseudoTableType.none) return; // pseudo table tokens already in correct position
3600        if (this.propertyToken != null) return; // columnToProperty() already called
3601        if (! ((this.partToken != null) && (this.objectToken != null))) return; // 既然是 column.property , 那么 partToken and objectToken 不能为空
3602
3603        this.propertyToken = this.partToken;
3604        this.partToken = this.objectToken;
3605        this.objectToken = this.schemaToken;
3606
3607        this.setDbObjectTypeDirectly(EDbObjectType.column);
3608    }
3609
3610    public void appendObjectName(TObjectName objectName){
3611        if (this.databaseToken != null){
3612            this.serverToken = this.databaseToken;
3613        }
3614        if (this.schemaToken != null){
3615            this.databaseToken = this.schemaToken;
3616        }
3617        if (this.objectToken != null){
3618            this.schemaToken = this.objectToken;
3619        }
3620        this.objectToken = this.partToken;
3621        this.partToken = objectName.getPartToken();
3622        this.setEndTokenDirectly(this.partToken);
3623    }
3624
3625    private TSourceToken commentString;
3626
3627    public void setCommentString(TSourceToken commentString) {
3628        this.commentString = commentString;
3629    }
3630
3631    public TSourceToken getCommentString() {
3632
3633        return commentString;
3634    }
3635
3636
3637    /**
3638     * The X and Y position of this objectName in the SQL
3639     *
3640     * @return coordinate in string text
3641     */
3642    public String coordinate(){
3643        return this.getStartToken().lineNo+","+this.getEndToken().columnNo;
3644    }
3645
3646
3647    /**
3648     * @deprecated replaced by {@link EDbObjectType}.
3649     *
3650     * this is not an object, like sysdate function in oracle database
3651     */
3652    public final static int ttobjNotAObject = -1;
3653
3654    /**
3655     * @deprecated replaced by {@link EDbObjectType}.
3656     * object type can't be determined.
3657     */
3658    public final static int ttobjUnknown = 0;
3659
3660    /**
3661     * @deprecated replaced by {@link EDbObjectType}.
3662     * column in table, objectToken is table if specified, and partToken is column name.
3663     */
3664    public final static int ttobjColumn = 1;
3665
3666    /**
3667     * @deprecated replaced by {@link EDbObjectType}.
3668     * column alias in objectToken.
3669     */
3670    public final static int ttobjColumnAlias = 2;
3671
3672    /**
3673     * @deprecated replaced by {@link EDbObjectType}.
3674     * table name in objectToken.
3675     */
3676    public final static int ttobjTable = 3;
3677
3678
3679    /**
3680     * @deprecated replaced by {@link EDbObjectType}.
3681     * parameter name in objectToken.
3682     */
3683    public final static int ttobjParameter = 9;
3684
3685    /**
3686     * @deprecated replaced by {@link EDbObjectType}.
3687     * variable name in objectToken.
3688     */
3689    public final static int ttobjVariable = 10;
3690
3691
3692    /**
3693     * @deprecated replaced by {@link EDbObjectType#method}.
3694     *  column method like SetXY below, column method in {@link #methodToken}, and colomn name in {@link #partToken}.
3695     *<p>   UPDATE Cities
3696     *<p>   SET Location.SetXY(23.5, 23.5)
3697     *
3698     *
3699     */
3700    public final static int ttobjColumnMethod = 11;
3701
3702    /**
3703     * Named argument parameter name in function calls.
3704     * <p>Example: In Snowflake FLATTEN(INPUT => parse_json(col), outer => TRUE),
3705     * "INPUT" and "outer" are named argument parameter names, NOT column references.
3706     * <p>These should be skipped during column resolution and data lineage analysis.
3707     */
3708    public final static int ttobjNamedArgParameter = 12;
3709
3710    /**
3711     * @deprecated replaced by {@link EDbObjectType}.
3712     * function name in {@link #objectToken}
3713     */
3714    public final static int ttobjFunctionName = 13;
3715
3716
3717    /**
3718     * @deprecated replaced by {@link EDbObjectType#constraint}.
3719     * constraint name in {@link #objectToken}
3720     */
3721    public final static int ttobjConstraintName = 19;
3722
3723    /**
3724     * @deprecated replaced by {@link EDbObjectType}.
3725     * string constant in {@link #objectToken}
3726     */
3727    public final static int ttobjStringConstant = 23;
3728
3729
3730    /**
3731     * @deprecated replaced by {@link EDbObjectType}.
3732     * attribute name is in {@link #partToken}
3733     */
3734    public final static int ttobjAttribute = 26;
3735
3736
3737    /**
3738     * @deprecated replaced by {@link EDbObjectType}.
3739     * datatype was not represented by a TObjectName object, this constant was used in source tokens that consist of  TTypeName.
3740     */
3741    public final static int ttobjDatatype = 30;
3742
3743    /**
3744     * @deprecated replaced by {@link EDbObjectType}.
3745     *  schema name in {@link #schemaToken}
3746     */
3747    public final static int ttobjSchemaName = 31;
3748
3749
3750    /**
3751     * @deprecated replaced by {@link EDbObjectType}.
3752     * postgresql
3753     * Positional Parameters, $1, $1[1], $1[1,10]
3754     * parameter name is in {@link #partToken} of $1,
3755     * and parameter name is in {@link #objectToken} of $1.columnName,
3756     * and column name is in {@link #partToken}
3757     */
3758
3759    public final static int ttobjPositionalParameters = 61;
3760
3761
3762
3763    private void setColumnTokenOfPositionalParameters(TSourceToken column){
3764        this.objectToken = this.partToken;
3765        this.partToken = column;
3766    }
3767
3768    private int objectType = ttobjUnknown;
3769
3770
3771    /**
3772     * @deprecated replaced by {@link EDbObjectType}.
3773     * this type is used in TObjectNameList, when objects in TObjectNameList includes more than
3774     * one type, objtype of that TObjectNameList was set to ttobjMixed.
3775     *
3776     * removed since v2.9.2.5
3777     */
3778   // public final static int ttobjMixed = 100;
3779
3780    /**
3781     * @deprecated replaced by {@link EDbObjectType#library}.
3782     * removed since v2.9.2.5
3783     */
3784   // public final static  int ttObjLibrary = 72;
3785
3786    /**
3787     * @deprecated replaced by {@link EDbObjectType#oracleHint}.
3788     * removed since v2.9.2.5
3789     */
3790   // public final static  int ttObjOracleHint = 70;
3791
3792    /**
3793     * @deprecated replaced by {@link EDbObjectType#fieldName}.
3794     * check {@link gudusoft.gsqlparser.nodes.TExpression#getFieldName()} for more
3795     * removed since v2.9.2.5
3796     */
3797   // public final static int ttobjFieldName = 51;
3798
3799    /**
3800     * @deprecated replaced by {@link EDbObjectType#miningModel}.
3801     * removed since v2.9.2.5
3802     */
3803    // public final static int ttobjMiningModel = 46;
3804
3805    /**
3806     * @deprecated replaced by {@link EDbObjectType#materializedView}.
3807     * removed since v2.9.2.5
3808     */
3809    // public final static int ttobjMaterializedView = 44;
3810
3811    /**
3812     * @deprecated replaced by {@link EDbObjectType#indextype}.
3813     * removed since v2.9.2.5
3814     */
3815   // public final static int ttobjIndexType = 42;
3816
3817    /**
3818     * @deprecated replaced by {@link EDbObjectType#operator}.
3819     * removed since v2.9.2.5
3820     */
3821    // public final static int ttobjOperator = 40;
3822
3823    /**
3824     * @deprecated replaced by {@link EDbObjectType#server}.
3825     * server name in {@link #serverToken}
3826     *
3827     * removed since v2.9.2.5
3828     */
3829  //  public final static int ttobjServerName = 32;
3830
3831    /**
3832     * @deprecated replaced by {@link EDbObjectType#sequence}.
3833     * Sequence name in {@link #objectToken}
3834     *
3835     * removed since v2.9.2.5
3836     */
3837   // public final static int ttobjSequence = 29;
3838
3839    /**
3840     * @deprecated replaced by {@link EDbObjectType#plsql_package}.
3841     * package name in {@link #objectToken}
3842     *
3843     * removed since v2.9.2.5
3844     */
3845   // public final static int ttobjPackage = 28;
3846
3847    /**
3848     * @deprecated replaced by {@link EDbObjectType#alias}.
3849     * alias name in {@link #objectToken}
3850     *
3851     * removed since v2.9.2.5
3852     */
3853   // public final static int ttobjAliasName = 25;
3854
3855
3856    /**
3857     * @deprecated replaced by {@link EDbObjectType#trigger}.
3858     * Trigger name in {@link #objectToken}
3859     *
3860     * removed since v2.9.2.5
3861     */
3862   // public final static int ttobjTrigger = 24;
3863
3864    /**
3865     * @deprecated replaced by {@link EDbObjectType#database}.
3866     * Database name in {@link #objectToken}
3867     *
3868     * removed since v2.9.2.5
3869     */
3870   // public final static int ttobjDatabaseName = 22;
3871
3872    /**
3873     * @deprecated replaced by {@link EDbObjectType#transaction}.
3874     * Transaction name in {@link #objectToken}
3875     *
3876     * removed since v2.9.2.5
3877     */
3878    // public final static int ttobjTransactionName = 21;
3879
3880
3881    /**
3882     * @deprecated replaced by {@link EDbObjectType#user_defined_type}.
3883     * type name in {@link #objectToken}
3884     *
3885     * removed since v2.9.2.5
3886     */
3887   // public final static int ttobjTypeName = 27;
3888
3889    /**
3890     * @deprecated replaced by {@link EDbObjectType#property}.
3891     * property name in {@link #propertyToken}
3892     *
3893     * removed since v2.9.2.5
3894     */
3895   // public final static int ttobjPropertyName = 20;
3896
3897    /**
3898     * @deprecated replaced by {@link EDbObjectType#view}.
3899     * view name in {@link #objectToken}
3900     *
3901     * removed since v2.9.2.5
3902     */
3903   // public final static int ttobjViewName = 18;
3904
3905    /**
3906     * @deprecated replaced by {@link EDbObjectType#cursor}.
3907     * cursor name in {@link #objectToken}
3908     *
3909     * removed since v2.9.2.5
3910     */
3911  //  public final static int ttobjCursorName = 17;
3912
3913    /**
3914     * @deprecated replaced by {@link EDbObjectType#materializedView}.
3915     * materialized view name in {@link #objectToken}
3916     *
3917     * removed since v2.9.2.5
3918     */
3919   // public final static int ttobjMaterializedViewName = 16;
3920
3921    /**
3922     * @deprecated replaced by {@link EDbObjectType#index}.
3923     * index name in {@link #objectToken}
3924     *
3925     * removed since v2.9.2.5
3926     */
3927   // public final static int ttobjIndexName = 15;
3928
3929    /**
3930     * @deprecated replaced by {@link EDbObjectType#label}.
3931     * label name in {@link #objectToken}
3932     *
3933     * removed since v2.9.2.5
3934     */
3935   // public final static int ttobjLabelName = 14;
3936
3937    /**
3938     * @deprecated replaced by {@link EDbObjectType#procedure}.
3939     * procedure name in {@link #objectToken}
3940     *
3941     * removed since v2.9.2.5
3942     */
3943   // public final static int ttobjProcedureName = 12;
3944
3945    /**
3946     * @deprecated replaced by {@link EDbObjectType#variable}.
3947     * table variable in objectToken.
3948     *
3949     * removed since v2.9.2.5
3950     */
3951   // public final static int ttobjTableVar = 8;
3952
3953    /**
3954     * @deprecated replaced by {@link EDbObjectType#cte}.
3955     * table name in objectToken.
3956     *
3957     * removed since v2.9.2.5
3958     */
3959    // public final static int ttobjTableCTE = 5;
3960
3961    /**
3962     * @deprecated replaced by {@link EDbObjectType}.
3963     * table name in objectToken.
3964     */
3965    // public final static int ttobjTableTemp = 6;
3966
3967    /**
3968     * @deprecated replaced by {@link EDbObjectType}.
3969     */
3970    //  public final static int ttobjTablePivot = 7;
3971
3972    /**
3973     * @deprecated replaced by {@link EDbObjectType#table_alias}.
3974     * table alias in objectToken
3975     *
3976     * removed since v2.9.2.5
3977     */
3978   // public final static int ttObjTableAlias = 4;
3979}