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 dbvdameng:
1037                if ( (getColumnNameOnly().compareToIgnoreCase ("sysdate") == 0)
1038                        || (getColumnNameOnly().compareToIgnoreCase ("nextval") == 0)
1039                        || (getColumnNameOnly().compareToIgnoreCase ("rownum") == 0)
1040                        || (getColumnNameOnly().compareToIgnoreCase ("level") == 0)
1041                        || (getColumnNameOnly().compareToIgnoreCase ("user") == 0)
1042                ){
1043                    setObjectType(TObjectName.ttobjNotAObject);
1044                    lcResult = false;
1045                }
1046                if ((toString().startsWith(":"))&&(toString().indexOf(".") == -1))
1047                {
1048                    setObjectType(TObjectName.ttobjNotAObject);
1049                    lcResult = false;
1050                }
1051                break;
1052            case dbvmssql:
1053                if ((getColumnNameOnly().compareToIgnoreCase ("system_user") == 0)
1054                ){
1055                    //setObjectType(TObjectName.ttobjNotAObject);
1056                    lcResult = false;
1057                }
1058                break;
1059            case dbvmysql:
1060                if (toString().startsWith("\"")){
1061                    // "X" is a string literal
1062                    lcResult = false;
1063                }
1064                // Skip reserved keyword check if the token was converted to IDENT (264)
1065                // by the yyparse() keyword-as-column-name lookahead
1066                if (getPartToken() != null && getPartToken().tokencode == 264) {
1067                    break;
1068                }
1069                if (keywordChecker.isKeyword(toString(),EDbVendor.dbvmysql,"6.0",true)){
1070                    isReservedKeyword = true;
1071                    lcResult = false;
1072                }
1073                break;
1074            case dbvteradata:
1075                if ((getObjectString().length() == 0)&&((getColumnNameOnly().compareToIgnoreCase ("account") == 0)
1076                        ||(getColumnNameOnly().compareToIgnoreCase ("current_date") == 0)
1077                        ||(getColumnNameOnly().compareToIgnoreCase ("current_role") == 0)
1078                        ||(getColumnNameOnly().compareToIgnoreCase ("current_time") == 0)
1079                        ||(getColumnNameOnly().compareToIgnoreCase ("current_timestamp") == 0)
1080                        ||(getColumnNameOnly().compareToIgnoreCase ("current_user") == 0)
1081                        ||(getColumnNameOnly().compareToIgnoreCase ("database") == 0)
1082                        ||((getColumnNameOnly().compareToIgnoreCase ("date") == 0)&&( this.getDbObjectType() != EDbObjectType.column ))
1083                        ||(getColumnNameOnly().compareToIgnoreCase ("profile") == 0)
1084                        ||(getColumnNameOnly().compareToIgnoreCase ("role") == 0)
1085                        ||(getColumnNameOnly().compareToIgnoreCase ("session") == 0)
1086                        ||(getColumnNameOnly().compareToIgnoreCase ("time") == 0)
1087                        ||(getColumnNameOnly().compareToIgnoreCase ("user") == 0)
1088                        ||(getColumnNameOnly().compareToIgnoreCase ("sysdate") == 0)
1089                )){
1090                    lcResult = false;
1091                }
1092                break;
1093            case dbvpostgresql:
1094                if (toString().startsWith("$")){
1095                    if ((toString().charAt(1) >= '0')
1096                            &&(toString().charAt(1) <= '9')){
1097                        this.setDbObjectType(EDbObjectType.variable);
1098                        lcResult = false;
1099                    }
1100                }
1101                break;
1102            case dbvbigquery:
1103                if ((getColumnNameOnly().compareToIgnoreCase ("CURRENT_DATE") == 0)
1104                         ||(getColumnNameOnly().compareToIgnoreCase ("CURRENT_TIME") == 0)
1105                        ||(getColumnNameOnly().compareToIgnoreCase ("CURRENT_TIMESTAMP") == 0)
1106                ){
1107                    //setObjectType(TObjectName.ttobjNotAObject);
1108                    lcResult = false;
1109                }
1110                break;
1111        }
1112
1113       if(lcResult){
1114           validate_column_status = TBaseType.VALIDATED_CAN_BE_A_COLUMN_NAME;
1115       }else{
1116           validate_column_status = TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME;
1117       }
1118
1119       return lcResult;
1120
1121    }
1122
1123    /**
1124     * Check if a string represents a numeric literal (integer or decimal).
1125     * Examples: "5", "10", "3.14", ".5"
1126     */
1127    private static boolean isNumericLiteral(String text) {
1128        if (text == null || text.isEmpty()) return false;
1129        boolean hasDigit = false;
1130        boolean hasDot = false;
1131        for (int i = 0; i < text.length(); i++) {
1132            char c = text.charAt(i);
1133            if (c >= '0' && c <= '9') {
1134                hasDigit = true;
1135            } else if (c == '.' && !hasDot) {
1136                hasDot = true;
1137            } else {
1138                return false;
1139            }
1140        }
1141        return hasDigit;
1142    }
1143
1144    private boolean expandStarColumns = false;
1145    ArrayList<String> expandedStarColumns = new ArrayList<>();
1146
1147    public ArrayList<String> getColumnsLinkedToStarColumn() {
1148        if (expandStarColumns) return expandedStarColumns;
1149
1150        //TTable sourceTable = this.getSourceTable();
1151        if (getSourceTableList().size() > 0){
1152            for( int i = 0;i < getSourceTableList().size();i++){
1153                for(String c:getSourceTableList().get(i).getExpandedStarColumns()){
1154                    expandedStarColumns.add(c);
1155                }
1156            }
1157        }
1158
1159        expandStarColumns = true;
1160        return expandedStarColumns;
1161
1162    }
1163
1164    private ArrayList<String> columnsLinkedToStarColumn = new ArrayList<String>();
1165
1166    private TResultColumnList columnsLinkedToStar;
1167
1168    public void setColumnsLinkedToStar(TResultColumnList columnsLinkedToStar) {
1169        this.columnsLinkedToStar = columnsLinkedToStar;
1170        for(TResultColumn rc:columnsLinkedToStar){
1171            if (rc.getColumnAlias()!= ""){
1172                columnsLinkedToStarColumn.add(rc.getColumnAlias());
1173            }else{
1174                columnsLinkedToStarColumn.add(rc.getColumnNameOnly());
1175            }
1176        }
1177
1178        this.setDbObjectTypeDirectly(EDbObjectType.column);
1179    }
1180
1181    /**
1182     * if this is a star column(column name is *), and the value of this star column
1183     * is derived from a subquery, then, this field points to the select list in the subquery
1184     *
1185     * @return the select list in the subquery
1186     */
1187    public TResultColumnList getColumnsLinkedToStar() {
1188        return columnsLinkedToStar;
1189    }
1190
1191    /**
1192     * Set the table this column belongs to. Used by parser internally.
1193     *
1194     * @param sourceTable table contains this column
1195     */
1196    public void setSourceTable(TTable sourceTable) {
1197
1198        // INSERT INTO "omni"."omni_upload_t1a8067802b804755b1d29ee935b3b0bc" VALUES ($1, $2, $3, $4, $5)
1199        // if this objectname is $1, then just return
1200        if (this.getDbObjectType() == EDbObjectType.parameter) {
1201            // to avoid this parameter been checked in TAttributeResolver preVisit(TObjectName attribute)
1202            this.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_TABLE_IN_OLD_ALGORITHM);
1203            return;
1204        }
1205
1206        this.sourceTable = sourceTable;
1207        // 如果 column token 的 tokentype 为 ETokenType.ttkeyword, 那么调整为 ETokenType.ttidentifier
1208        if ((this.getPartToken() != null)&&(this.getPartToken().tokentype == ETokenType.ttkeyword)){
1209            if ((this.getPartToken().getDbObjectType() == EDbObjectType.column)||(this.getPartToken().getDbObjectType() == EDbObjectType.unknown)){
1210                this.getPartToken().tokentype = ETokenType.ttidentifier;
1211            }
1212        }
1213        this.setDbObjectTypeDirectly(EDbObjectType.column);
1214
1215        // If this column was previously an orphan and is now linked to a table,
1216        // remove it from the orphanColumns list of its owning statement
1217        if (sourceTable != null && this.isOrphanColumn() && this.getOwnStmt() != null) {
1218            TObjectNameList orphanColumns = this.getOwnStmt().getOrphanColumns();
1219            if (orphanColumns != null) {
1220                orphanColumns.removeElement(this);
1221            }
1222            this.setOrphanColumn(false);
1223        }
1224    }
1225
1226    public void setSourceTableBySQLResolver(TCustomSqlStatement sqlStatement, TAttributeNode attributeNode, TTable newSourceTable) {
1227        // 如果 column token 的 tokentype 为 ETokenType.ttkeyword, 那么调整为 ETokenType.ttidentifier
1228        if ((this.getPartToken() != null)&&(this.getPartToken().tokentype == ETokenType.ttkeyword)){
1229            if ((this.getPartToken().getDbObjectType() == EDbObjectType.column)||(this.getPartToken().getDbObjectType() == EDbObjectType.unknown)){
1230                this.getPartToken().tokentype = ETokenType.ttidentifier;
1231            }
1232        }
1233
1234        if ((this.sourceTable != null) && (this.sourceTable.equals(newSourceTable))) return;
1235
1236        if ((this.getResolveStatus() == TBaseType.RESOLVED_AND_FOUND ) 
1237                || (newSourceTable.getTableType() != ETableSource.subquery)){// 关联到 subquery 的 column 本能算真正找到 table,因此还不能去掉 orphan column
1238            if (sqlStatement.getOrphanColumns().removeElement(this)){
1239                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
1240                    TBaseType.log(String.format("Remove orphan column <%s> find in old algorithm",this.toString()),TLog.WARNING,this);
1241                }
1242                // remove the waring in sql statement's error syntax list
1243                TCustomSqlStatement currentStatement = sqlStatement;
1244                while (currentStatement != null) {
1245                    if (currentStatement.getSyntaxHints() != null) {
1246                        for(int i=0; i<currentStatement.getSyntaxHints().size(); i++) {
1247                            TSyntaxError syntaxError = currentStatement.getSyntaxHints().get(i);
1248                            if (syntaxError.errortype == EErrorType.sphint) {
1249                                if ((syntaxError.lineNo == this.getStartToken().lineNo)||(syntaxError.columnNo == this.getStartToken().columnNo)) {
1250                                    currentStatement.getSyntaxHints().remove(i);
1251                                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
1252                                        TBaseType.log(String.format("Remove orphan column <%s> warning message in old algorithm", this.toString()), TLog.WARNING, this);
1253                                    }
1254                                    break;
1255                                }
1256                            }
1257                        }
1258                    }
1259                    currentStatement = currentStatement.getParentStmt();
1260                }
1261            }
1262        }else{
1263            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);
1264        }
1265
1266        if (this.sourceTable != null){
1267            if (this.sourceTable.getLinkedColumns().removeElement(this)){
1268                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
1269                    TBaseType.log(String.format("Remove <%s> at addr: %s from table <%s> that found in old algorithm, new linked table is: %s"
1270                            ,this.toString(),Integer.toHexString(this.hashCode()),this.sourceTable.toString(),newSourceTable.toString())
1271                            ,TLog.WARNING,this);
1272                }
1273            }
1274        }
1275        this.sourceTable = newSourceTable;
1276        this.sourceTable.getLinkedColumns().addObjectName(this);
1277
1278        if (this.getSourceColumn() == null){
1279           // if (attributeNode.isAttributeCreatedFromAliasColumn()){
1280                this.setSourceColumn(attributeNode.getSubLevelResultColumn());
1281           // }
1282        }
1283
1284        this.setDbObjectTypeDirectly(EDbObjectType.column);
1285    }
1286
1287    /**
1288     * Get the <b>immediate</b> source table where this column is visible in the current scope.
1289     *
1290     * <p>This returns the table/subquery/CTE that directly exposes this column in the FROM clause,
1291     * NOT the final physical table after tracing through subqueries or CTEs.</p>
1292     *
1293     * <p>To get the final physical table (after tracing through all layers), use:
1294     * {@code getResolution().getColumnSource().getFinalTable()}</p>
1295     *
1296     * <h3>Example</h3>
1297     * <pre>{@code
1298     * SELECT title FROM (SELECT * FROM books) sub
1299     *
1300     * For the 'title' column in outer SELECT:
1301     * - getSourceTable()           → TTable for subquery 'sub' (tableType=subquery)
1302     * - resolution.getFinalTable() → TTable for 'books' (the physical table)
1303     * }</pre>
1304     *
1305     * @return The immediate source table, or null if not resolved
1306     * @see #sourceTable
1307     * @see gudusoft.gsqlparser.resolver2.model.ColumnSource#getFinalTable()
1308     */
1309    public TTable getSourceTable() {
1310        // If new resolver determined this column is ambiguous, don't return old Phase 1 value
1311        // This ensures ambiguous columns are treated as orphan by the formatter
1312        // EXCEPTION: Star columns (*) should preserve their sourceTable for proper output
1313        if (resolution != null && resolution.isAmbiguous()) {
1314            String colName = getColumnNameOnly();
1315            if (colName != null && colName.equals("*")) {
1316                // Star columns keep their Phase 1 sourceTable
1317                return sourceTable;
1318            }
1319            if (gudusoft.gsqlparser.TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
1320                System.out.println("[TObjectName.getSourceTable] Column '" + colName +
1321                    "' is AMBIGUOUS - returning null instead of " +
1322                    (sourceTable != null ? sourceTable.getName() : "null"));
1323            }
1324            return null;
1325        }
1326        if (resolution == null && sourceTable != null) {
1327            if (gudusoft.gsqlparser.TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE) {
1328                System.out.println("[TObjectName.getSourceTable] Column '" + getColumnNameOnly() +
1329                    "' has NO resolution - using Phase 1 sourceTable: " + sourceTable.getName());
1330            }
1331        }
1332        return sourceTable;
1333    }
1334
1335    public void setResolveStatus(int resolveStatus) {
1336        this.resolveStatus = resolveStatus;
1337
1338        if (this.resolveStatus == TBaseType.RESOLVED_AND_FOUND){
1339            this.setDbObjectTypeDirectly(EDbObjectType.column);
1340        }
1341
1342    }
1343
1344    private int resolveStatus = TBaseType.NOT_RESOLVED_YET;
1345
1346    public int getResolveStatus() {
1347        return resolveStatus;
1348    }
1349
1350    public void   TObjectName(){
1351
1352    }
1353
1354    private TObjectNameList columnAttributes = null;
1355
1356    private boolean  subscripts;
1357    private  TIndirection indirection;
1358
1359    /**
1360     * PostgreSQL column with array types
1361     * <pre>
1362     *     CREATE TABLE sal_emp (
1363     *          name            text,
1364     *          pay_by_quarter  integer[],
1365     *          schedule        text[][]
1366     *     );
1367     * </pre>
1368     * In the above SQL, this method returns true for <code>pay_by_quarter</code> column.
1369     *
1370     * @return true if this objectName is array type
1371     */
1372    public boolean isSubscripts() {
1373        return subscripts;
1374    }
1375
1376    public void setIndirection(TIndirection indirection) {
1377        if(indirection == null) return;
1378
1379        this.indirection = indirection;
1380        // setup the exceptReplaceClause of the last indirection to the parent object which is set in the .y bnf file
1381        if (indirection.getIndices() != null){
1382            if (indirection.getIndices().getElement(indirection.getIndices().size()-1).getAttributeName() != null){
1383                setExceptReplaceClause(indirection.getIndices().getElement(indirection.getIndices().size()-1).getAttributeName().getExceptReplaceClause());
1384
1385            }
1386        }
1387
1388        // possible syntax, support in postgresql only in current version:
1389        // [ indirection ], list in [] was indirection
1390        //
1391        // tablename[.column]
1392        // tablename[.*]
1393        // $1[.somecolumn]
1394        //
1395        // mytable[.arraycolumn[4]]
1396        // mytable[.two_d_column[17][34]]
1397        // $1[[10:42]]
1398        //
1399
1400        if (this.getObjectType() == TObjectName.ttobjPositionalParameters){
1401            if(indirection.isRealIndices()){
1402                //$1[10:42]
1403                this.subscripts = true;
1404            }else{
1405                //$1.somecolumn
1406               this.setColumnTokenOfPositionalParameters(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1407            }
1408        }else{
1409            if(indirection.isRealIndices()){
1410                if (indirection.getIndices().size() == 1){
1411                    // arraycolumn[4]
1412                    this.subscripts = true;
1413                }else if (indirection.getIndices().size() >= 2){
1414                   if (!indirection.getIndices().getElement(0).isRealIndices()){
1415                        // mytable[.arraycolumn[4]]
1416                        // mytable[.two_d_column[17][34]]
1417                     //  this.setPartTokenOfIndirection(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1418                       this.subscripts = true;
1419                     //  this.indirection.getIndices().remove(0);
1420                   }
1421                }
1422            }
1423
1424            //else{
1425                // 首先查找 : 和 [ 分隔符,如果找到,在该分隔符前的是 column,如果没有找到按照一般 qualified name 规则处理
1426                // https://docs.snowflake.com/en/user-guide/querying-semistructured.html
1427                int elementIndex = -1;
1428                for(int i=0;i<indirection.getIndices().size();i++){
1429                    TIndices tmp = indirection.getIndices().getElement(i);
1430                    if ((tmp.getStartToken().tokencode == ':')||(tmp.getStartToken().tokencode == '[')||(tmp.getStartToken().tokencode == TBaseType.bind_v)){
1431                        elementIndex = i;
1432                        break;
1433                    }
1434                }
1435                if (elementIndex >= 0){
1436                    // 找到了 : 和 [ 分隔符
1437                    if (elementIndex == 0){
1438                        // snowflake, <column>:<level1_element>
1439                        // everything already perfect, nothing need to be changed
1440                        partToken.setDbObjectType(EDbObjectType.column);
1441                    }else if (elementIndex == 1){
1442                        // snowflake, table.column:<level1_element>
1443                        objectToken = partToken;
1444                        objectToken.setDbObjectType(EDbObjectType.table);
1445                        partToken = indirection.getIndices().getElement(elementIndex-1).getAttributeName().getPartToken();
1446                        partToken.setDbObjectType(EDbObjectType.column);
1447                    }else if (elementIndex == 2){
1448                        // snowflake, schema.table.column:<level1_element>
1449                        schemaToken = partToken;
1450                        schemaToken.setDbObjectType(EDbObjectType.schema);
1451                        objectToken = indirection.getIndices().getElement(elementIndex-2).getAttributeName().getPartToken();
1452                        objectToken.setDbObjectType(EDbObjectType.table);
1453                        partToken = indirection.getIndices().getElement(elementIndex-1).getAttributeName().getPartToken();
1454                        partToken.setDbObjectType(EDbObjectType.column);
1455                    }
1456                }else{
1457                    // 一般 qualified name 规则处理
1458                    if (indirection.getIndices().size() == 1){
1459                        this.setPartTokenOfIndirection(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1460                    }else if (indirection.getIndices().size() == 2){
1461                        schemaToken = partToken;
1462                        schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
1463                        objectToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1464                        objectToken.setDbObjType(ttobjTable);
1465                        partToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1466                        partToken.setDbObjType(ttobjColumn);
1467                    }else if (indirection.getIndices().size() == 3){
1468                        // db.schema.tablename.column
1469                        databaseToken = partToken;
1470                        databaseToken.setDbObjectType(EDbObjectType.database);
1471                        partToken = indirection.getIndices().getElement(2).getAttributeName().getPartToken();
1472                        partToken.setDbObjectType(EDbObjectType.column);
1473                        objectToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1474                        objectToken.setDbObjectType(EDbObjectType.table);
1475                        schemaToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1476                        schemaToken.setDbObjectType(EDbObjectType.schema);
1477                    }
1478                }
1479
1480//                if (indirection.getIndices().size() == 1){
1481//                    if ((indirection.getIndices().getElement(0).getStartToken().tokencode == ':')
1482//                        ||(indirection.getIndices().getElement(0).getStartToken().tokencode == TBaseType.bind_v))
1483//                    {
1484//                        // snowflake, <column>:<level1_element>
1485//
1486//                    }else{
1487//                        // tablename[.column]
1488//                        // tablename[.*]
1489//                        this.setPartTokenOfIndirection(indirection.getIndices().getElement(0).getAttributeName().getPartToken());
1490//                    }
1491//                }else if (indirection.getIndices().size() == 2){
1492//                    if ((indirection.getIndices().getElement(0).getStartToken().tokencode == ':')
1493//                            ||(indirection.getIndices().getElement(0).getStartToken().tokencode == TBaseType.bind_v))
1494//                    {
1495//                        // snowflake, <column>:<level1_element>
1496//
1497//                    }else {
1498//                        // schema.tablename.column
1499//                        schemaToken = partToken;
1500//                        schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
1501//                        objectToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1502//                        objectToken.setDbObjType(ttobjTable);
1503//                        partToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1504//                        partToken.setDbObjType(ttobjColumn);
1505//                    }
1506//                }else if (indirection.getIndices().size() == 3){
1507//                    // db.schema.tablename.column
1508//                    databaseToken = partToken;
1509//                    databaseToken.setDbObjectType(EDbObjectType.database);
1510//                    partToken = indirection.getIndices().getElement(2).getAttributeName().getPartToken();
1511//                    partToken.setDbObjectType(EDbObjectType.column);
1512//                    objectToken = indirection.getIndices().getElement(1).getAttributeName().getPartToken();
1513//                    objectToken.setDbObjectType(EDbObjectType.table);
1514//                    schemaToken = indirection.getIndices().getElement(0).getAttributeName().getPartToken();
1515//                    schemaToken.setDbObjectType(EDbObjectType.schema);
1516//                }
1517
1518          //  }
1519
1520        }
1521
1522    }
1523
1524    /**
1525     * Array element of this objectName
1526     * <pre>
1527     *     select arraycolumn[4] from t;
1528     * </pre>
1529     * In the above SQL, this method returns <code>[4]</code> of this objectName.
1530     *
1531     * @return array element of this objectName
1532     * @see gudusoft.gsqlparser.nodes.TIndirection
1533     */
1534    public TIndirection getIndirection() {
1535        return indirection;
1536    }
1537
1538    private void setPartTokenOfIndirection(TSourceToken column){
1539        parseTablename();
1540        this.partToken = column;
1541        this.partToken.setDbObjType(ttobjColumn);
1542    }
1543
1544    public void setPropertyToken(TSourceToken propertyToken) {
1545        this.propertyToken = propertyToken;
1546    }
1547
1548    public TSourceToken getAtsign() {
1549        return atsign;
1550    }
1551
1552    public TSourceToken getMethodToken() {
1553
1554        return methodToken;
1555    }
1556
1557    public TSourceToken getPropertyToken() {
1558        return propertyToken;
1559    }
1560
1561    /**
1562     *  The server part of this objectName: [server.][database.][schema.]object
1563     *
1564     * @return server part of the objectName
1565     */
1566    public TSourceToken getServerToken() {
1567        return serverToken;
1568    }
1569
1570    public TSourceToken getExclamationmark() {
1571
1572        return exclamationmark;
1573    }
1574
1575    /**
1576     *
1577     * The database link part <code>remoreserver</code> in this objectName: scott.emp@remoreserver
1578     *
1579     * @return database link
1580     */
1581    public TObjectName getDblink() {
1582
1583        return dblink;
1584    }
1585
1586    /**
1587     *  The database part of this objectName: [server.][database.][schema.]object
1588     *
1589     * @return database part of the objectName
1590     */
1591    public TSourceToken getDatabaseToken() {
1592        return databaseToken;
1593    }
1594
1595
1596    private boolean tableDetermined = true;
1597
1598    public void setTableDetermined(boolean tableDetermined) {
1599        this.tableDetermined = tableDetermined;
1600    }
1601
1602
1603    /**
1604     * Sometime, a non-qualified column can't be linked to a table without additional metadata from database.
1605     * <pre>
1606     *     select name from emp, dept
1607     * </pre>
1608     * In the above SQL, the <code>name</code> column can't be determined which table it belongs to.
1609     *
1610     * Below is a more complicated SQL that shows the relationship between column and table.
1611     * <pre>
1612     *   select
1613     *          s2.s2t1a1,
1614     *          s3.s3t1a1
1615     *   from
1616     *       (
1617     *         select *
1618     *           from subselect2table1 s2t1
1619     *       ) s2,
1620     *       (
1621     *          select *
1622     *             from  subselect3table1, subselect3table2
1623     *       ) s3
1624     * </pre>
1625     *
1626     * column s2t1a1 was linked to subselect2table1, {@link #isTableDetermined()} returns true for this column.
1627     * <br> column s3t1a1 was linked to both subselect3table1 and subselect3table2
1628     * due to lack of meta information from database, {@link #isTableDetermined()} returns false for this column.
1629     * <p>
1630     * Provide database metadata will help GSP links the column to the table correctly.
1631     *
1632     * @return true if this column can be linked to a table without doubt.
1633     * @see gudusoft.gsqlparser.TGSqlParser#setMetaDatabase
1634     */
1635    public boolean isTableDetermined() {
1636        return tableDetermined;
1637    }
1638
1639    /**
1640     * used in Oracle and teradata SQL syntax
1641     * <p>teradata:
1642     * <p>column.attribute()
1643     * <p>column.attribute().attribute() 
1644     * @param attributes
1645     */
1646    public void attributesToPropertyToken(TObjectNameList attributes){
1647        if (attributes.size() == 1){
1648            this.propertyToken = attributes.getObjectName(0).getPartToken();
1649        }
1650    }
1651
1652    public void setColumnAttributes(TObjectNameList columnAttributes) {
1653        this.columnAttributes = columnAttributes;
1654    }
1655
1656    /**
1657     * The data type of this column is structured UDT, this method returns the column's attributes.
1658     * 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>.
1659     * <pre>
1660     *
1661     *          CREATE TYPE school_record AS (
1662     *           school_name VARCHAR(20),
1663     *           GPA         FLOAT);
1664     *
1665     *          CREATE TYPE college_record AS (
1666     *           school school_record,
1667     *           major  VARCHAR(20),
1668     *           minor  VARCHAR(20));
1669     *
1670     *          CREATE TABLE student_record (
1671     *           student_id  INTEGER,
1672     *           Last_name   VARCHAR(20),
1673     *           First_name  VARCHAR(20),
1674     *           high_school school_record,
1675     *           college     college_record);
1676     *
1677     *          SELECT student_id, last_name, first_name,
1678     *           high_school.school_name(), high_school.GPA(),
1679     *           college.school().school_name(), college.school().GPA(),
1680     *           college.major(), college.minor()
1681     *          FROM student_record;
1682     *
1683     *          SELECT *.ALL FROM student_record;
1684     *          SELECT student_record.*.ALL;
1685     * </pre>
1686     * Take this column <code>college.school().school_name()</code> for example, the partToken of this objectName
1687     * should be <code>college</code>, and the value returned by this method should be
1688     * <code>school().school_name()</code>
1689     * <p>
1690     * PLEASE NOTE THAT CURRENT VERSION CAN'T HANDLE THE ABOVE SQL CORRECTLY.
1691     *
1692     * @return attributes of this structured UDT column
1693     */
1694    public TObjectNameList getColumnAttributes() {
1695        return columnAttributes;
1696    }
1697
1698    /**
1699     * Used internally.
1700     * @deprecated use {@link #setDbObjectType} instead
1701     *
1702     * @param objectType object type of this objectName
1703     */
1704    public void setObjectType(int objectType) {
1705        if (this.objectType == objectType) return;
1706        this.objectType = objectType;
1707        // set this object type to source token
1708        switch(this.getObjectType()){
1709            case TObjectName.ttobjTable:
1710           // case TObjectName.ttobjTableTemp:
1711            //    case TObjectName.ttobjTableVar:
1712                this.parseTablename();
1713                this.objectToken.setDbObjType(this.objectType);
1714                if (dbObjectType != EDbObjectType.stage){ // not already set to stage
1715                    dbObjectType = EDbObjectType.table;
1716                }
1717
1718                if ((!TSQLEnv.supportSchema(this.dbvendor))&&(this.schemaToken != null)){
1719                    this.databaseToken = this.schemaToken;
1720                    this.schemaToken = null;
1721                }
1722                break;
1723//            case TObjectName.ttobjTableCTE:
1724//                this.parseTablename();
1725//                this.objectToken.setDbObjType(this.objectType);
1726//                dbObjectType = EDbObjectType.cte;
1727//                break;
1728//            case ttObjLibrary:
1729//                this.parseTablename();
1730//                this.objectToken.setDbObjType(this.objectType);
1731//                dbObjectType = EDbObjectType.library;
1732//                break;
1733            case TObjectName.ttobjColumn:
1734                this.partToken.setDbObjType(this.objectType);
1735                dbObjectType = EDbObjectType.column;
1736                break;
1737            case TObjectName.ttobjColumnAlias:
1738                if ((this.objectToken == null) && (this.partToken != null)){
1739                   this.parseObjectName();
1740                }
1741                this.objectToken.setDbObjType(this.objectType);
1742                dbObjectType = EDbObjectType.column_alias;
1743                break;
1744//            case TObjectName.ttObjTableAlias:
1745//                this.parseObjectName();
1746//                this.objectToken.setDbObjType(this.objectType);
1747//                dbObjectType = EDbObjectType.table_alias;
1748//                break;
1749            case TObjectName.ttobjParameter:
1750                this.parseObjectName();
1751                this.objectToken.setDbObjType(this.objectType);
1752                dbObjectType = EDbObjectType.parameter;
1753                break;
1754            case TObjectName.ttobjVariable:
1755                this.parseVariableName();
1756                this.objectToken.setDbObjType(this.objectType);
1757                dbObjectType = EDbObjectType.variable;
1758                break;
1759            case TObjectName.ttobjColumnMethod:
1760                if (dbObjectType != EDbObjectType.method){
1761                    this.parseColumnMethodName();
1762                    this.partToken.setDbObjType(this.ttobjColumn);
1763                    this.methodToken.setDbObjType(this.ttobjColumnMethod);
1764                    dbObjectType = EDbObjectType.method;
1765                }
1766                break;
1767//            case TObjectName.ttobjProcedureName:
1768//                this.parseFunctionName();
1769//                this.objectToken.setDbObjType(this.objectType);
1770//                dbObjectType = EDbObjectType.procedure;
1771//                break;
1772            case TObjectName.ttobjFunctionName:
1773                this.parseFunctionName();
1774                this.objectToken.setDbObjType(this.objectType);
1775                dbObjectType = EDbObjectType.function;
1776                break;
1777//            case TObjectName.ttobjLabelName:
1778//                this.parseObjectName();
1779//                this.objectToken.setDbObjType(this.objectType);
1780//                dbObjectType = EDbObjectType.label;
1781//                break;
1782//            case TObjectName.ttobjIndexName:
1783//                this.parseObjectName();
1784//                this.objectToken.setDbObjType(this.objectType);
1785//                dbObjectType = EDbObjectType.index;
1786//                break;
1787//            case TObjectName.ttobjMaterializedViewName:
1788//                this.parseObjectName();
1789//                this.objectToken.setDbObjType(this.objectType);
1790//                dbObjectType = EDbObjectType.materializedView;
1791//                break;
1792//            case TObjectName.ttobjViewName:
1793//                this.parseObjectName();
1794//                this.objectToken.setDbObjType(this.objectType);
1795//                dbObjectType = EDbObjectType.view;
1796//                break;
1797//            case TObjectName.ttobjCursorName:
1798//                this.parseObjectName();
1799//                this.objectToken.setDbObjType(this.objectType);
1800//                dbObjectType = EDbObjectType.cursor;
1801//                break;
1802            case TObjectName.ttobjConstraintName:
1803                this.parseObjectName();
1804                this.objectToken.setDbObjType(this.objectType);
1805                dbObjectType = EDbObjectType.constraint;
1806                break;
1807//            case TObjectName.ttobjPropertyName:
1808//                this.propertyToken.setDbObjType(this.objectType);
1809//                dbObjectType = EDbObjectType.property;
1810//                break;
1811//            case TObjectName.ttobjTransactionName:
1812//                this.parseObjectName();
1813//                this.objectToken.setDbObjType(this.objectType);
1814//                dbObjectType = EDbObjectType.transaction;
1815//                break;
1816//            case TObjectName.ttobjDatabaseName:
1817//                this.parseObjectName();
1818//                this.objectToken.setDbObjType(this.objectType);
1819//                dbObjectType = EDbObjectType.database;
1820//                break;
1821            case TObjectName.ttobjStringConstant:
1822                this.parseObjectName();
1823                this.objectToken.setDbObjType(this.objectType);
1824                break;
1825//            case TObjectName.ttobjAliasName:
1826//                this.parseObjectName();
1827//                this.objectToken.setDbObjType(this.objectType);
1828//                dbObjectType = EDbObjectType.alias;
1829//                break;
1830            case TObjectName.ttobjAttribute:
1831                this.partToken.setDbObjType(this.objectType);
1832                dbObjectType = EDbObjectType.attribute;
1833                break;
1834
1835            case TObjectName.ttobjPositionalParameters:
1836                dbObjectType = EDbObjectType.parameter;
1837                break;
1838//           case TObjectName.ttobjTypeName:
1839//               this.parseObjectName();
1840//               this.objectToken.setDbObjType(this.objectType);
1841//               dbObjectType = EDbObjectType.user_defined_type;
1842//               break;
1843//            case TObjectName.ttobjPackage:
1844//                this.parseObjectName();
1845//                this.objectToken.setDbObjType(this.objectType);
1846//                dbObjectType = EDbObjectType.plsql_package;
1847//                break;
1848//            case TObjectName.ttobjSequence:
1849//                this.parseObjectName();
1850//                this.objectToken.setDbObjType(this.objectType);
1851//                dbObjectType = EDbObjectType.sequence;
1852//                break;
1853//            case TObjectName.ttobjTrigger:
1854//                this.parseObjectName();
1855//                this.objectToken.setDbObjType(this.objectType);
1856//                dbObjectType = EDbObjectType.trigger;
1857//                break;
1858            default:
1859                break;
1860        }
1861    }
1862
1863    public void setDbObjectType(EDbVendor dbVendor, EDbObjectType dbObjectType) {
1864        this.dbvendor = dbVendor;
1865        this.setDbObjectType(dbObjectType);
1866    }
1867
1868    public void setDbObjectTypeDirectly(EDbObjectType dbObjectType) {
1869        this.dbObjectType = dbObjectType;
1870    }
1871    /**
1872     * Set object type of this objectName
1873     *
1874     * @param dbObjectType database object type
1875     */
1876    public void setDbObjectType(EDbObjectType dbObjectType) {
1877        if (this.dbObjectType == dbObjectType) return;
1878        if (this.dbObjectType == EDbObjectType.stage) return;
1879        // TODO, 如果已经被设定为某个对象类型,不应该再次设置,但如果下面的语句执行,会导致部分测试用例失败,需要查具体原因
1880        // if (this.dbObjectType != EDbObjectType.unknown) return;
1881
1882        EDbObjectType prev = this.dbObjectType;
1883        this.dbObjectType = dbObjectType;
1884        if (prev == EDbObjectType.unknown){
1885            switch (dbObjectType){
1886                case column:
1887                    parseColumnName();
1888                    break;
1889                case table:
1890                case index:
1891                case synonym:
1892                case macro:
1893                case view:
1894                case stage:
1895                case task:
1896                case stream:
1897                case TEMP_TABLE:
1898                case pipe:
1899                case security_policy:
1900
1901                case plsql_package:
1902                case trigger:
1903                case transaction:
1904                case user_defined_type:
1905                case property:
1906                case cursor:
1907                case label:
1908                case table_alias:
1909                case partitionScheme:
1910                    parseTablename();
1911                    break;
1912                case library:
1913                    parseTablename();
1914                    break;
1915                case function:
1916                    parseFunctionName();
1917                    break;
1918                case procedure:
1919                case materializedView:
1920                    parseFunctionName();
1921                    break;
1922                case alias:
1923                case module:
1924                case sequence:
1925                    parseTablename();
1926                    break;
1927                case database:
1928                    this.objectToken = this.partToken;
1929                    this.databaseToken = null;
1930                    this.partToken = null;
1931                    break;
1932                case variable:
1933                    parseVariableName();
1934                    break;
1935                case schema:
1936                    this.databaseToken = this.objectToken;
1937                    this.objectToken = this.partToken;
1938                    this.schemaToken = this.partToken;
1939                    break;
1940                case method:
1941                    this.parseColumnMethodName();
1942                    this.partToken.setDbObjType(this.ttobjColumn);
1943                    //this.methodToken.setDbObjType(this.ttobjColumnMethod);
1944                    this.methodToken.setDbObjectType(EDbObjectType.method);
1945                    break;
1946                case cte:
1947                    parseObjectName();
1948                    break;
1949                case hint: //sql server hint like nolock
1950                    parseObjectName();
1951                    break;
1952                default:
1953                    break;
1954            }
1955        }
1956    }
1957
1958    private EDbObjectType dbObjectType = EDbObjectType.unknown;
1959
1960    /**
1961     * The database object type of this objectName such as table, view, column for example.
1962     * If object type is {@link gudusoft.gsqlparser.EDbObjectType#column}, {@link #getPartToken} represents
1963     * the column name, for all other object type, the name of this database object is stored in {@link #getObjectToken}
1964     *
1965     * @return database object type
1966     */
1967    public EDbObjectType getDbObjectType() {
1968        return dbObjectType;
1969    }
1970
1971    /**
1972     * @deprecated use {@link #getDbObjectType()} instead.
1973     *
1974     * @return the type of database object or variable this objectName represents for.
1975     */
1976    public int getObjectType() {
1977
1978        return objectType;
1979    }
1980
1981
1982    public void setAtsign(TSourceToken atsign) {
1983        this.atsign = atsign;
1984    }
1985
1986    public void setDblink(TObjectName dblink) {
1987        dblink.setDbObjectType(EDbObjectType.dblink);
1988        this.dblink = dblink;
1989    }
1990
1991    public void setDblink(TObjectName dblink, boolean linkToDB) {
1992        setDblink(dblink);
1993
1994        if (linkToDB){
1995            if (dblink.numberOfPart == 1){
1996                this.databaseToken = dblink.getPartToken();
1997            }
1998        }
1999    }
2000
2001    private TSourceToken serverToken = null; //sql server
2002    private TSourceToken databaseToken = null; //sql server
2003    // schemaToken.objectToken.partToken@dblink, schemaToken, partToken, and dblink is optional
2004    private TSourceToken schemaToken;
2005    private TSourceToken objectToken;
2006
2007    /*
2008     * part is a part of the object. This identifier lets you refer to a part of a schema object,
2009     * such as a column or a partition of a table. Not all types of objects have parts.
2010     */
2011    private TSourceToken partToken;
2012    private TSourceToken propertyToken = null;
2013    private TSourceToken methodToken = null;
2014    private TSourceToken atsign; //@
2015    private TObjectName dblink;
2016
2017    // Additional parts for deeply nested struct field access (BigQuery, etc.)
2018    // Stores parts beyond the 6 standard tokens (server, database, schema, object, part, property)
2019    private java.util.List<TSourceToken> additionalParts = null;
2020
2021    // ===== Phase 5: Normalized identifier cache (transient, not serialized) =====
2022    // These caches reduce repeated normalize() calls for the same TObjectName
2023    private transient String normalizedServer;
2024    private transient String normalizedDatabase;
2025    private transient String normalizedSchema;
2026    private transient String normalizedTable;
2027    private transient String normalizedColumn;
2028    private transient long cacheFingerprint = 0;  // Profile fingerprint for cache invalidation
2029
2030    public void setServerToken(TSourceToken serverToken) {
2031        this.serverToken = serverToken;
2032    }
2033
2034    public void setDatabaseToken(TSourceToken databaseToken, boolean implicit) {
2035        this.isImplicitDatabase = implicit;
2036        setDatabaseToken(databaseToken);
2037    }
2038
2039    public void setDatabaseToken(TSourceToken databaseToken) {
2040        this.databaseToken = databaseToken;
2041    }
2042
2043    public void setObjectToken(TSourceToken objectToken) {
2044        this.objectToken = objectToken;
2045    }
2046
2047    public void setPartToken(TSourceToken partToken) {
2048        this.partToken = partToken;
2049    }
2050
2051    public void setMethodToken(TSourceToken methodToken) {
2052        this.methodToken = methodToken;
2053    }
2054
2055    public void setSchemaToken(TSourceToken schemaToken, boolean implicit) {
2056        this.isImplicitSchema = implicit;
2057        setSchemaToken(schemaToken);
2058    }
2059
2060    public void setSchemaToken(TSourceToken schemaToken) {
2061
2062        this.schemaToken = schemaToken;
2063    }
2064
2065    public void setPackageToken(TSourceToken packageToken) {
2066        this.packageToken = packageToken;
2067    }
2068
2069    /**
2070     * Oracle package name
2071     *
2072     * @return the source token of Oracle package name.
2073     */
2074    public TSourceToken getPackageToken() {
2075        return packageToken;
2076    }
2077
2078    private TSourceToken packageToken = null;
2079
2080
2081    /**
2082     * The object part of this objectName such as table name, view name.
2083     *
2084     * @return object part of this objectName
2085     */
2086    public TSourceToken getObjectToken() {
2087        return objectToken;
2088    }
2089
2090    /**
2091     * The column name of this objectName if {@link #getDbObjectType} is {@link EDbObjectType#column}.
2092     * {@link #getColumnToken} returns the same value.
2093     *
2094     * @return the column name
2095     */
2096    public TSourceToken getPartToken() {
2097        return partToken;
2098    }
2099
2100    /**
2101     * The schema name of this objectName.
2102     *
2103     * @return schema name
2104     */
2105    public TSourceToken getSchemaToken() {
2106        return schemaToken;
2107    }
2108
2109
2110    private String schemaString;
2111    private String objectString;
2112    private String partString;
2113
2114    /**
2115     * String text of the package name.
2116     *
2117     * @return string of the package name,return null if empty.
2118     */
2119    public String getPackageString(){
2120        if (getPackageToken() != null) return  getPackageToken().toString();
2121        else return "";
2122    }
2123
2124    /**
2125     * String text of the server name
2126     *
2127     * @return string of the server name,return null if empty.
2128     */
2129    public String getServerString(){
2130        if (getServerToken() != null) return  getServerToken().toString();
2131        else return "";
2132    }
2133
2134    /**
2135     * String text of the database name
2136     *
2137     * @return string of the database name,return null if empty.
2138     */
2139    public String getDatabaseString(){
2140        if (isImplicitDatabase ) return "";
2141        else if (getDatabaseToken() != null) return getDatabaseToken().toString();
2142        else if ((this.dbObjectType == EDbObjectType.database) && (getObjectToken() != null)){
2143            return getObjectToken().toString();
2144        }
2145        else return "";
2146    }
2147
2148    /**
2149     * String text of schema name in a qualified name of a schema object.
2150     *
2151     *
2152     * @return string of schema name, return null if empty.
2153     */
2154    public String getSchemaString() {
2155        if (isImplicitSchema) return "";
2156        else if (schemaToken != null)
2157            return schemaToken.getAstext();
2158        else
2159            return "" ;
2160    }
2161
2162    private TSQLEnv sqlEnv = null;
2163
2164    public void setSqlEnv(TSQLEnv sqlEnv) {
2165        this.sqlEnv = sqlEnv;
2166    }
2167
2168
2169
2170    /**
2171     * This is the schema fetched from the SQLEnv. Not the direct qualified schema name of this object
2172     * search this table in the current default database and schema.
2173     *
2174     * If this is a qualified schema object, then return {@link #getSchemaString()}
2175     *
2176     * This method is only valid when the {@link #dbObjectType} is a schema object.
2177     *
2178     * @return schema name fetched from the SQLEnv
2179     */
2180    public String getImplictSchemaString() {
2181        String implictSchema = null;
2182        // Objects with a db_link refer to remote databases and should not
2183        // inherit the current session's default schema
2184        if (this.dblink != null) return null;
2185        if (this.implictSchemaName != null) return this.implictSchemaName;
2186
2187        if (schemaToken != null) return schemaToken.toString();
2188        if (getSchemaString().length() > 0) return  getSchemaString();
2189
2190        if (sqlEnv == null) return null;
2191
2192        TSQLSchema s = searchImplicitSchema();
2193        if (s != null){
2194            implictSchema = s.getName();
2195        }
2196
2197        return implictSchema;
2198    }
2199
2200    private String implictDatabaseName;
2201    private String implictSchemaName;
2202
2203    public void setImplictDatabaseName(String implictDatabaseName) {
2204        this.isImplicitDatabase = true;
2205        this.implictDatabaseName = implictDatabaseName;
2206    }
2207
2208    public void setImplictSchemaName(String implictSchemaName) {
2209        this.isImplicitSchema = true;
2210        this.implictSchemaName = implictSchemaName;
2211    }
2212
2213    public String getImplictDatabaseString() {
2214        String implictDatabase = null;
2215        // Objects with a db_link refer to remote databases and should not
2216        // inherit the current session's default catalog
2217        if (this.dblink != null) return null;
2218        if (implictDatabaseName != null) return implictDatabaseName;
2219
2220        if (getDatabaseString().length() > 0) return  getDatabaseString();
2221
2222        if (sqlEnv == null) return null;
2223        TSQLSchema s = searchImplicitSchema();
2224        if (s != null){
2225            TSQLCatalog c = s.getCatalog();
2226            if (c != null){
2227                implictDatabase = c.getName();
2228            }
2229        }
2230
2231        return implictDatabase;
2232    }
2233
2234    protected TSQLSchema searchImplicitSchema(){
2235        TSQLSchema s = null;
2236        if (sqlEnv == null) return null;
2237        switch (dbObjectType){
2238            case table:
2239            case view:
2240                TSQLTable t = sqlEnv.searchTable(".."+this.getObjectString());
2241                if (t != null){
2242                    s = t.getSchema();
2243                }
2244
2245                break;
2246            case function:
2247            case procedure:
2248                TSQLFunction f = sqlEnv.searchFunction(".."+this.getObjectString());
2249                if (f != null){
2250                    s = f.getSchema();
2251                }
2252                break;
2253            default:
2254                break;
2255        }
2256
2257        return s;
2258    }
2259
2260    /**
2261     * The table name of this objectName, it's the same value as {@link #getObjectToken} if {@link #getDbObjectType}
2262     * is {@link gudusoft.gsqlparser.EDbObjectType#table}
2263     *
2264     * @return table name
2265     */
2266    public  TSourceToken getTableToken(){
2267        if (objectToken == null) return  null;
2268        else return objectToken;
2269    }
2270
2271    /**
2272     * String text of the table name
2273     *
2274     * @return string of the table name, return null if empty.
2275     */
2276    public String getTableString(){
2277        if (objectToken == null) return  "";
2278//        else if (!((dbObjectType == EDbObjectType.table)||(dbObjectType == EDbObjectType.view))){
2279//            return "";
2280//        }
2281        else return objectToken.toString();
2282    }
2283
2284    /**
2285     * String text of the object name
2286     *
2287     * @return string of the object name, return null if empty.
2288     */
2289    public String getObjectString() {
2290        if (objectToken != null)
2291            return objectToken.getAstext();
2292        else
2293            return "" ;
2294    }
2295
2296    /**
2297     * String text of the part name
2298     *
2299     * @return string of the part name, return null if empty.
2300     */
2301    public String getPartString() {
2302        if (partToken != null)
2303            return partToken.getAstext();
2304        else
2305            return "" ;
2306    }
2307
2308    // ===== Phase 5: Cached normalized getters (reduce repeated normalize() calls) =====
2309
2310    private transient gudusoft.gsqlparser.sqlenv.IdentifierService identifierService;
2311
2312    /**
2313     * Lazy initialization of IdentifierService for normalized identifier caching
2314     */
2315    private gudusoft.gsqlparser.sqlenv.IdentifierService getIdentifierService() {
2316        if (identifierService == null && sqlEnv != null) {
2317            gudusoft.gsqlparser.sqlenv.IdentifierProfile profile = gudusoft.gsqlparser.sqlenv.IdentifierProfile.forVendor(
2318                sqlEnv.getDBVendor(),
2319                gudusoft.gsqlparser.sqlenv.IdentifierProfile.VendorFlags.defaults()
2320            );
2321            identifierService = new gudusoft.gsqlparser.sqlenv.IdentifierService(profile, null);
2322        }
2323        return identifierService;
2324    }
2325
2326    /**
2327     * Get normalized database string with caching (Phase 5 optimization).
2328     *
2329     * <p>This method caches the normalized database name to avoid repeated normalize() calls.
2330     * The cache is invalidated automatically when the IdentifierProfile changes (e.g., vendor switch).
2331     *
2332     * @return normalized database name, or empty string if not available
2333     */
2334    public String getNormalizedDatabaseString() {
2335        if (sqlEnv == null) return getDatabaseString();
2336
2337        try {
2338            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2339            if (service == null) return getDatabaseString();
2340
2341            long currentFingerprint = service.getProfile().getFingerprint();
2342
2343            // Check if cache is valid (not null and fingerprint matches)
2344            if (normalizedDatabase == null || cacheFingerprint != currentFingerprint) {
2345                String raw = getDatabaseString();
2346                if (raw != null && !raw.isEmpty()) {
2347                    normalizedDatabase = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotCatalog);
2348                } else {
2349                    normalizedDatabase = "";
2350                }
2351                cacheFingerprint = currentFingerprint;
2352            }
2353
2354            return normalizedDatabase;
2355        } catch (Throwable t) {
2356            // Fallback to non-cached on any error
2357            return getDatabaseString();
2358        }
2359    }
2360
2361    /**
2362     * Get normalized schema string with caching (Phase 5 optimization).
2363     *
2364     * <p>This method caches the normalized schema name to avoid repeated normalize() calls.
2365     * The cache is invalidated automatically when the IdentifierProfile changes.
2366     *
2367     * @return normalized schema name, or empty string if not available
2368     */
2369    public String getNormalizedSchemaString() {
2370        if (sqlEnv == null) return getSchemaString();
2371
2372        try {
2373            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2374            if (service == null) return getSchemaString();
2375
2376            long currentFingerprint = service.getProfile().getFingerprint();
2377
2378            // Check if cache is valid
2379            if (normalizedSchema == null || cacheFingerprint != currentFingerprint) {
2380                String raw = getSchemaString();
2381                if (raw != null && !raw.isEmpty()) {
2382                    normalizedSchema = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotSchema);
2383                } else {
2384                    normalizedSchema = "";
2385                }
2386                cacheFingerprint = currentFingerprint;
2387            }
2388
2389            return normalizedSchema;
2390        } catch (Throwable t) {
2391            // Fallback to non-cached
2392            return getSchemaString();
2393        }
2394    }
2395
2396    /**
2397     * Get normalized table string with caching (Phase 5 optimization).
2398     *
2399     * <p>This method caches the normalized table name to avoid repeated normalize() calls.
2400     * The cache is invalidated automatically when the IdentifierProfile changes.
2401     *
2402     * @return normalized table name, or empty string if not available
2403     */
2404    public String getNormalizedTableString() {
2405        if (sqlEnv == null) return getTableString();
2406
2407        try {
2408            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2409            if (service == null) return getTableString();
2410
2411            long currentFingerprint = service.getProfile().getFingerprint();
2412
2413            // Check if cache is valid
2414            if (normalizedTable == null || cacheFingerprint != currentFingerprint) {
2415                String raw = getTableString();
2416                if (raw != null && !raw.isEmpty()) {
2417                    normalizedTable = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotTable);
2418                } else {
2419                    normalizedTable = "";
2420                }
2421                cacheFingerprint = currentFingerprint;
2422            }
2423
2424            return normalizedTable;
2425        } catch (Throwable t) {
2426            // Fallback to non-cached
2427            return getTableString();
2428        }
2429    }
2430
2431    /**
2432     * Get normalized column string with caching (Phase 5 optimization).
2433     *
2434     * <p>This method caches the normalized column name (from partToken) to avoid repeated normalize() calls.
2435     * The cache is invalidated automatically when the IdentifierProfile changes.
2436     *
2437     * @return normalized column name, or empty string if not available
2438     */
2439    public String getNormalizedColumnString() {
2440        if (sqlEnv == null) return getPartString();
2441
2442        try {
2443            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2444            if (service == null) return getPartString();
2445
2446            long currentFingerprint = service.getProfile().getFingerprint();
2447
2448            // Check if cache is valid
2449            if (normalizedColumn == null || cacheFingerprint != currentFingerprint) {
2450                String raw = getPartString();
2451                if (raw != null && !raw.isEmpty()) {
2452                    normalizedColumn = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotColumn);
2453                } else {
2454                    normalizedColumn = "";
2455                }
2456                cacheFingerprint = currentFingerprint;
2457            }
2458
2459            return normalizedColumn;
2460        } catch (Throwable t) {
2461            // Fallback to non-cached
2462            return getPartString();
2463        }
2464    }
2465
2466    /**
2467     * Get normalized server string with caching (Phase 5 optimization).
2468     *
2469     * <p>This method caches the normalized server name to avoid repeated normalize() calls.
2470     * The cache is invalidated automatically when the IdentifierProfile changes.
2471     *
2472     * @return normalized server name, or empty string if not available
2473     */
2474    public String getNormalizedServerString() {
2475        if (sqlEnv == null) return getServerString();
2476
2477        try {
2478            gudusoft.gsqlparser.sqlenv.IdentifierService service = getIdentifierService();
2479            if (service == null) return getServerString();
2480
2481            long currentFingerprint = service.getProfile().getFingerprint();
2482
2483            // Check if cache is valid
2484            if (normalizedServer == null || cacheFingerprint != currentFingerprint) {
2485                String raw = getServerString();
2486                if (raw != null && !raw.isEmpty()) {
2487                    // Use dotUnknown for server since there's no dotServer type
2488                    normalizedServer = service.normalize(raw, gudusoft.gsqlparser.sqlenv.ESQLDataObjectType.dotUnknown);
2489                } else {
2490                    normalizedServer = "";
2491                }
2492                cacheFingerprint = currentFingerprint;
2493            }
2494
2495            return normalizedServer;
2496        } catch (Throwable t) {
2497            // Fallback to non-cached
2498            return getServerString();
2499        }
2500    }
2501
2502
2503    public void setExclamationmark(TSourceToken exclamationmark) {
2504        this.exclamationmark = exclamationmark;
2505    }
2506
2507    private TSourceToken exclamationmark; // objectToken@!, ! is dblink
2508
2509    private Boolean isParsed = false;
2510
2511    private void parseObjectName(){
2512        parseTablename();
2513    }
2514
2515   private void parseTablename(){
2516       if ((this.dbObjectType == EDbObjectType.variable) ||(this.dbObjectType == EDbObjectType.stage))return;
2517
2518       switch (this.dbvendor){
2519           case dbvteradata:
2520           case dbvhive:
2521               if (objectToken != null){
2522                   databaseToken = objectToken;
2523                   //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
2524                   databaseToken.setDbObjectType(EDbObjectType.database);
2525                   schemaToken = objectToken;
2526               }
2527               objectToken = partToken;
2528               partToken = null;
2529
2530               break;
2531           default:
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                   schemaToken = objectToken;
2546                   schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
2547               }
2548
2549               objectToken = partToken;
2550               partToken = null;
2551               break;
2552       }
2553
2554       if (objectToken != null){
2555           objectToken.setDbObjectType(this.dbObjectType);
2556       }
2557    }
2558
2559    private void parseVariableName(){
2560        if (databaseToken != null){
2561            serverToken = databaseToken;
2562            //serverToken.setDbObjType(TObjectName.ttobjServerName);
2563            serverToken.setDbObjectType(EDbObjectType.server);
2564        }
2565
2566        if (schemaToken != null){
2567            databaseToken = schemaToken;
2568           // databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
2569            databaseToken.setDbObjectType(EDbObjectType.database);
2570        }
2571
2572        if (objectToken != null){
2573            if (partToken != null){
2574                schemaToken = objectToken;
2575                schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
2576                objectToken = partToken;
2577                partToken = null;
2578            }else{
2579
2580            }
2581        }else{
2582            objectToken = partToken;
2583            partToken = null;
2584        }
2585    }
2586
2587    private String ansiSchemaName;
2588    private String ansiCatalogName;
2589
2590
2591    /**
2592     *  In this SQL: select * from part1.part2,
2593     *  In Hive, MySQL and Teradata, part1 will be treated as a database name, returned in getDatabaseString(),
2594     *  while getSchemaString() return empty string.
2595     *
2596     *  However, TObjectName.getAnsiSchemaName() will return part1, which means it's a schema name.
2597     *
2598     *  If a table name is not qualified with a schema name, but GSP detect the schema for this table in the metadata
2599     *  then, this method will return this detected schema name.
2600     *
2601     * @return schema name
2602     */
2603    public String getAnsiSchemaName(){
2604        String ret = this.getSchemaString();
2605        if ((ret.length() == 0) && ((this.getImplictSchemaString() != null) && (!this.getImplictSchemaString().equalsIgnoreCase("default")))){
2606            ret = this.getImplictSchemaString();
2607        }
2608
2609        switch (dbvendor){
2610            case dbvmysql:
2611            case dbvhive:
2612            case dbvteradata:
2613            case dbvimpala:
2614                ret = this.getDatabaseString();
2615                break;
2616        }
2617        return ret;
2618    }
2619
2620    /**
2621     *   If a table name is not qualified with a database name, but GSP detect the database  for this table in the metadata
2622     *   then, this method will return this detected database name.
2623     *
2624     * @return
2625     */
2626    public String getAnsiCatalogName(){
2627        String ret = this.getDatabaseString();
2628        if (( ret.length() == 0) && (this.getImplictDatabaseString() != null) && (!this.getImplictDatabaseString().equalsIgnoreCase("default"))){
2629            ret = this.getImplictDatabaseString();
2630        }
2631
2632        switch (dbvendor){
2633            case dbvmysql:
2634            case dbvhive:
2635            case dbvteradata:
2636            case dbvimpala:
2637                ret = "";
2638                break;
2639        }
2640
2641        return  ret;
2642    }
2643
2644    private void parseFunctionName(){
2645        this.parseTablename();
2646     }
2647
2648    private void parseColumnMethodName(){
2649       // objectType = ttobjColumnMethod;
2650
2651        methodToken = objectToken;
2652        partToken = schemaToken;
2653
2654        objectToken = null;//;
2655        schemaToken = null;
2656     }
2657
2658    private void parseColumnName(){
2659        assert(partToken != null);
2660     }
2661
2662    public TObjectName(){
2663    }
2664
2665    /**
2666     *  List the number of parts made up this objectName
2667     *
2668     * @return the number of parts that made up this objectName
2669     */
2670    public int getNumberOfPart() {
2671        return numberOfPart;
2672    }
2673
2674    private int numberOfPart = 1;
2675
2676    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType){
2677        return new TObjectName(dbVendor,dbObjectType);
2678    }
2679
2680
2681    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1){
2682        return new TObjectName(dbVendor,dbObjectType,token1);
2683    }
2684
2685    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType, String str) {
2686        String[] parts = str.split("\\.");
2687        if (parts.length == 1) {
2688            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]));
2689        } else if (parts.length == 2) {
2690            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]), new TSourceToken(parts[1]));
2691        } else if (parts.length == 3) {
2692            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]), new TSourceToken(parts[1]), new TSourceToken(parts[2]));
2693        } else if (parts.length == 4) {
2694            return new TObjectName(dbVendor, dbObjectType, new TSourceToken(parts[0]), new TSourceToken(parts[1]), new TSourceToken(parts[2]), new TSourceToken(parts[3]));
2695        }
2696        return new TObjectName(dbVendor, dbObjectType, new TSourceToken(str));
2697    }
2698
2699
2700    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1,TSourceToken token2){
2701        return new TObjectName(dbVendor,dbObjectType,token1,token2);
2702    }
2703
2704    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1,TSourceToken token2,TSourceToken token3){
2705        return new TObjectName(dbVendor,dbObjectType,token1,token2,token3);
2706    }
2707
2708    public static TObjectName createObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token1,TSourceToken token2,TSourceToken token3,TSourceToken token4){
2709        return new TObjectName(dbVendor,dbObjectType,token1,token2,token3,token4);
2710    }
2711
2712    /**
2713     * @deprecated As of v2.0.7.1, please use {@link #TObjectName(EDbObjectType, TSourceToken)} instead.
2714     *
2715     * Class constructor specifying object name and object type.
2716     * <p>
2717     * Use {@link gudusoft.gsqlparser.TGSqlParser#parseObjectName} to create an objectName more than 2 parts.
2718     *
2719     * @param token           name of this object
2720     * @param dbObjectType   type of this object
2721     */
2722    private TObjectName(TSourceToken token,EDbObjectType dbObjectType){
2723        this(dbObjectType,token);
2724    }
2725
2726    public void splitNameInQuotedIdentifier(){
2727        if (this.dbvendor != EDbVendor.dbvbigquery && this.dbvendor != EDbVendor.dbvsnowflake) return;
2728        if (this.objectToken == null) return;
2729        TSourceToken token = this.objectToken;
2730
2731        // For Snowflake IDENTIFIER function, the token is a single-quoted string literal
2732        // which has quoteType notQuoted but starts with single quote
2733        boolean isSnowflakeSingleQuotedString = (this.dbvendor == EDbVendor.dbvsnowflake)
2734                && token.toString().startsWith("'");
2735
2736        if (getQuoteType() == EQuoteType.notQuoted && !isSnowflakeSingleQuotedString) return;
2737//        if ((this.dbvendor != EDbVendor.dbvbigquery)
2738//                &&(getQuoteType() == EQuoteType.doubleQuote)) return;
2739
2740        String tokenStr = token.toString();
2741        char outerQuoteChar = tokenStr.charAt(0);
2742        String s = TBaseType.getTextWithoutQuoted(tokenStr);
2743        String[] a = s.split("[.]");
2744        if (a.length == 1){
2745            // this.objectToken = token;
2746        }else if (a.length == 2){
2747            String objPart = a[1];
2748            String schemaPart = a[0];
2749
2750            // For Snowflake IDENTIFIER function with single-quoted string containing double-quoted parts
2751            // e.g., IDENTIFIER('"SCHEMA"."TABLE"') -> parts already have double quotes
2752            // For unquoted parts, e.g., IDENTIFIER('schema.table') -> use parts as-is
2753            boolean isSnowflakeIdentifierFunction = (this.dbvendor == EDbVendor.dbvsnowflake) && (outerQuoteChar == '\'');
2754
2755            if (isSnowflakeIdentifierFunction) {
2756                this.objectToken = new TSourceToken(objPart);
2757                this.schemaToken = new TSourceToken(schemaPart);
2758            } else {
2759                this.objectToken = new TSourceToken(outerQuoteChar + objPart + outerQuoteChar);
2760                this.schemaToken = new TSourceToken(outerQuoteChar + schemaPart + outerQuoteChar);
2761            }
2762        }else if (a.length == 3){
2763            String objPart = a[2];
2764            String schemaPart = a[1];
2765            String dbPart = a[0];
2766
2767            boolean isSnowflakeIdentifierFunction = (this.dbvendor == EDbVendor.dbvsnowflake) && (outerQuoteChar == '\'');
2768
2769            if (isSnowflakeIdentifierFunction) {
2770                this.objectToken = new TSourceToken(objPart);
2771                this.schemaToken = new TSourceToken(schemaPart);
2772                this.databaseToken = new TSourceToken(dbPart);
2773            } else {
2774                this.objectToken = new TSourceToken(outerQuoteChar + objPart + outerQuoteChar);
2775                this.schemaToken = new TSourceToken(outerQuoteChar + schemaPart + outerQuoteChar);
2776                this.databaseToken = new TSourceToken(outerQuoteChar + dbPart + outerQuoteChar);
2777            }
2778        }
2779
2780    }
2781
2782    private TObjectName(EDbVendor dbVendor){
2783        this.dbvendor = dbVendor;
2784    }
2785
2786    private TObjectName(EDbVendor dbVendor,EDbObjectType dbObjectType){
2787        this.dbvendor = dbVendor;
2788        this.dbObjectType = dbObjectType;
2789    }
2790
2791    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token){
2792        this.dbvendor = dbVendor;
2793        numberOfPart = 1;
2794        this.setStartToken(token);
2795        this.setEndToken(token);
2796
2797        this.dbObjectType = dbObjectType;
2798        switch (dbObjectType){
2799            case column:
2800                this.partToken = token;
2801                break;
2802            case method:
2803                this.methodToken = token;
2804                break;
2805            case table:
2806            case function:
2807            case procedure:
2808            case materializedView:
2809            case alias:
2810            case module:
2811            case sequence:
2812            case collation:
2813                this.objectToken = token;
2814                splitNameInQuotedIdentifier();
2815                break;
2816            default:
2817                this.objectToken = token;
2818                break;
2819        }
2820    }
2821
2822    private void initWithOneToken(EDbObjectType dbObjectType,TSourceToken token){
2823        numberOfPart = 1;
2824        this.setStartToken(token);
2825        this.setEndToken(token);
2826
2827        this.dbObjectType = dbObjectType;
2828        switch (dbObjectType){
2829            case column:
2830                this.partToken = token;
2831                break;
2832            case method:
2833                this.methodToken = token;
2834                break;
2835            case table:
2836            case function:
2837            case procedure:
2838            case materializedView:
2839            case alias:
2840            case module:
2841            case sequence:
2842            case collation:
2843            case stage:
2844                this.objectToken = token;
2845                splitNameInQuotedIdentifier();
2846                break;
2847            case namespace:
2848                this.schemaToken = token;
2849                break;
2850            default:
2851                this.objectToken = token;
2852                break;
2853        }
2854    }
2855
2856    private void initWithTwoTokens(EDbObjectType dbObjectType,TSourceToken token2,TSourceToken token1){
2857        initWithOneToken(dbObjectType,token1);
2858        numberOfPart = 2;
2859        this.setStartToken(token2);
2860        this.setEndToken(token1);
2861
2862        switch (dbObjectType){
2863            case column:
2864                this.objectToken = token2;
2865                break;
2866            case method:
2867                this.partToken = token2;
2868                break;
2869            case table:
2870            case function:
2871            case procedure:
2872            case materializedView:
2873            case alias:
2874            case module:
2875            case sequence:
2876            case collation:
2877            case stage:
2878                this.schemaToken = token2;
2879                break;
2880            case namespace:
2881                this.databaseToken = token2;
2882                break;
2883            default:
2884                this.schemaToken = token2;
2885                break;
2886        }
2887
2888    }
2889
2890    private void initWithThreeTokens(EDbObjectType dbObjectType,TSourceToken token3,TSourceToken token2,TSourceToken token1){
2891        initWithTwoTokens(dbObjectType,token2,token1);
2892        numberOfPart = 3;
2893        this.setStartToken(token3);
2894        this.setEndToken(token1);
2895
2896        switch (dbObjectType){
2897            case column:
2898                this.schemaToken = token3;
2899                break;
2900            case method:
2901                this.objectToken = token3;
2902                break;
2903            case table:
2904            case function:
2905            case procedure:
2906            case materializedView:
2907            case alias:
2908            case module:
2909            case sequence:
2910            case collation:
2911            case stage:
2912                this.databaseToken = token3;
2913                break;
2914            default:
2915                this.databaseToken = token3;
2916                break;
2917        }
2918
2919    }
2920
2921    private void initWithFourTokens(EDbObjectType dbObjectType,TSourceToken token4,TSourceToken token3,TSourceToken token2,TSourceToken token1){
2922        initWithThreeTokens(dbObjectType,token3,token2,token1);
2923        numberOfPart = 4;
2924        this.setStartToken(token4);
2925        this.setEndToken(token1);
2926
2927        switch (dbObjectType){
2928            case column:
2929                this.databaseToken = token4;
2930                break;
2931            case method:
2932                this.schemaToken = token4;
2933                break;
2934            case table:
2935            case function:
2936            case procedure:
2937            case materializedView:
2938            case alias:
2939            case module:
2940            case sequence:
2941            case collation:
2942                this.serverToken = token4;
2943                break;
2944            default:
2945                this.serverToken = token4;
2946                break;
2947        }
2948
2949    }
2950
2951    /**
2952     * @deprecated As of v2.0.7.1, please use {@link TObjectName#createObjectName(EDbVendor, EDbObjectType, TSourceToken)} instead.
2953     * 
2954     * @param dbObjectType
2955     * @param token
2956     */
2957    private TObjectName(EDbObjectType dbObjectType,TSourceToken token){
2958        initWithOneToken(dbObjectType,token);
2959    }
2960
2961    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token2,TSourceToken token1){
2962        this(dbVendor,dbObjectType,token1);
2963        numberOfPart = 2;
2964        this.setStartToken(token2);
2965        this.setEndToken(token1);
2966
2967        switch (dbObjectType){
2968            case column:
2969                this.objectToken = token2;
2970                break;
2971            case method:
2972                this.partToken = token2;
2973                break;
2974            case table:
2975            case function:
2976            case procedure:
2977            case materializedView:
2978            case alias:
2979            case module:
2980            case sequence:
2981            case collation:
2982                if (dbVendor == EDbVendor.dbvteradata){
2983                    this.databaseToken = token2;
2984                }else{
2985                    this.schemaToken = token2;
2986                }
2987
2988                break;
2989            default:
2990                this.schemaToken = token2;
2991                break;
2992        }
2993
2994    }
2995
2996
2997    /**
2998     * @deprecated since ver 2.5.9.8
2999     *
3000     * @param dbObjectType
3001     * @param token2
3002     * @param token1
3003     */
3004    private TObjectName(EDbObjectType dbObjectType,TSourceToken token2,TSourceToken token1){
3005        this(dbObjectType,token1);
3006        numberOfPart = 2;
3007        this.setStartToken(token2);
3008        this.setEndToken(token1);
3009
3010        switch (dbObjectType){
3011            case column:
3012                this.objectToken = token2;
3013                break;
3014            case method:
3015                this.partToken = token2;
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.schemaToken = token2;
3026                break;
3027            default:
3028                this.schemaToken = token2;
3029                break;
3030        }
3031    }
3032    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3033        this(dbVendor,dbObjectType,token2,token1);
3034        numberOfPart = 3;
3035        this.setStartToken(token3);
3036        this.setEndToken(token1);
3037
3038        switch (dbObjectType){
3039            case column:
3040                this.schemaToken = token3;
3041                break;
3042            case method:
3043                this.objectToken = token3;
3044                break;
3045            case table:
3046            case function:
3047            case procedure:
3048            case materializedView:
3049            case alias:
3050            case module:
3051            case sequence:
3052            case collation:
3053                this.databaseToken = token3;
3054                break;
3055            default:
3056                this.databaseToken = token3;
3057                break;
3058        }
3059
3060    }
3061
3062
3063    /**
3064     * @deprecated since ver 2.5.9.8
3065     *
3066     * @param dbObjectType
3067     * @param token3
3068     * @param token2
3069     * @param token1
3070     */
3071    private TObjectName(EDbObjectType dbObjectType,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3072        this(dbObjectType,token2,token1);
3073        numberOfPart = 3;
3074        this.setStartToken(token3);
3075        this.setEndToken(token1);
3076
3077        switch (dbObjectType){
3078            case column:
3079                this.schemaToken = token3;
3080                break;
3081            case method:
3082                this.objectToken = token3;
3083                break;
3084            case table:
3085            case function:
3086            case procedure:
3087            case materializedView:
3088            case alias:
3089            case module:
3090            case sequence:
3091            case collation:
3092                this.databaseToken = token3;
3093                break;
3094            default:
3095                this.databaseToken = token3;
3096                break;
3097        }
3098    }
3099
3100    private TObjectName(EDbVendor dbVendor, EDbObjectType dbObjectType,TSourceToken token4,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3101        this(dbVendor,dbObjectType,token3,token2,token1);
3102        numberOfPart = 4;
3103        this.setStartToken(token4);
3104        this.setEndToken(token1);
3105
3106        switch (dbObjectType){
3107            case column:
3108                this.databaseToken = token4;
3109                break;
3110            case method:
3111                this.schemaToken = token4;
3112                break;
3113            case table:
3114            case function:
3115            case procedure:
3116            case materializedView:
3117            case alias:
3118            case module:
3119            case sequence:
3120            case collation:
3121                this.serverToken = token4;
3122                break;
3123            default:
3124                this.serverToken = token4;
3125                break;
3126        }
3127
3128    }
3129
3130    /**
3131     * @deprecated since ver 2.5.9.8
3132     *
3133     * @param dbObjectType
3134     * @param token4
3135     * @param token3
3136     * @param token2
3137     * @param token1
3138     */
3139    private TObjectName(EDbObjectType dbObjectType,TSourceToken token4,TSourceToken token3,TSourceToken token2,TSourceToken token1){
3140        this(dbObjectType,token3,token2,token1);
3141        numberOfPart = 4;
3142        this.setStartToken(token4);
3143        this.setEndToken(token1);
3144
3145        switch (dbObjectType){
3146            case column:
3147                this.databaseToken = token4;
3148                break;
3149            case method:
3150                this.schemaToken = token4;
3151                break;
3152            case table:
3153            case function:
3154            case procedure:
3155            case materializedView:
3156            case alias:
3157            case module:
3158            case sequence:
3159            case collation:
3160                this.serverToken = token4;
3161                break;
3162            default:
3163                this.serverToken = token4;
3164                break;
3165        }
3166    }
3167
3168    /**
3169     * @deprecated As of v2.0.7.1, please use {@link #TObjectName(EDbObjectType, TSourceToken, TSourceToken)} instead.
3170     *
3171     * Class constructor specifying object, part name and object type.
3172     * Use this constructor to create a <code>table.column</code> objectName.
3173     * Use {@link gudusoft.gsqlparser.TGSqlParser#parseObjectName} to create an objectName more than 2 parts.
3174     *
3175     * @param pObjectToken    name of this object, usually it's the table name
3176     * @param pPartToken      name of the column
3177     * @param dbObjectType    type of this object, usually it's {@link gudusoft.gsqlparser.EDbObjectType#column}
3178     */
3179    private TObjectName(TSourceToken pObjectToken,TSourceToken pPartToken,EDbObjectType dbObjectType){
3180        this(dbObjectType,pObjectToken,pPartToken);
3181    }
3182
3183    public void init(Object arg1)
3184    {
3185        partToken = (TSourceToken)arg1;
3186        numberOfPart = 1;
3187        this.setStartToken(partToken);
3188        this.setEndToken(partToken);
3189    }
3190
3191    public void init(Object arg1, Object arg2)
3192    {
3193        if (arg1 instanceof EDbObjectType){
3194            initWithOneToken((EDbObjectType)arg1,(TSourceToken) arg2);
3195
3196        }else{
3197            numberOfPart = 0;
3198            objectToken = (TSourceToken)arg1;
3199            partToken = (TSourceToken)arg2;
3200            if (partToken != null) numberOfPart++;
3201            if (objectToken != null) numberOfPart++;
3202
3203
3204            if(objectToken != null){
3205                this.setStartToken(objectToken);
3206            }else{
3207                this.setStartToken(partToken);
3208            }
3209
3210            if (partToken != null){
3211                this.setEndToken(partToken);
3212            }else{
3213                this.setEndToken(objectToken);
3214            }
3215        }
3216    }
3217
3218    public void init(EDbObjectType dbObjectType, Object arg1, Object arg2, Object arg3){
3219        numberOfPart = 0;
3220        if (arg1 != null) numberOfPart++;
3221        if (arg2 != null) numberOfPart++;
3222        if (arg3 != null) numberOfPart++;
3223
3224        this.dbObjectType = dbObjectType;
3225        this.setStartToken((TSourceToken)arg1);
3226        this.setEndToken((TSourceToken)arg3);
3227        switch (this.dbObjectType){
3228            case column:
3229                schemaToken = (TSourceToken)arg1;
3230                objectToken = (TSourceToken)arg2;
3231                partToken = (TSourceToken)arg3;
3232                break;
3233            case table:
3234            case function:
3235            case procedure:
3236            case materializedView:
3237            case module:
3238            case sequence:
3239                databaseToken = (TSourceToken) arg1;
3240                schemaToken = (TSourceToken)arg2;
3241                objectToken = (TSourceToken)arg3;
3242                break;
3243            case alias:
3244                break;
3245            default:
3246                break;
3247        }
3248
3249    }
3250
3251    public void init(Object arg1, Object arg2, Object arg3)
3252    {
3253        if (arg1 instanceof EDbObjectType){
3254            initWithTwoTokens((EDbObjectType)arg1,(TSourceToken) arg2,(TSourceToken) arg3);
3255        }else{
3256            numberOfPart = 0;
3257            if (arg1 != null) numberOfPart++;
3258            if (arg2 != null) numberOfPart++;
3259            if (arg3 != null) numberOfPart++;
3260
3261            if (dbvendor == EDbVendor.dbvteradata){
3262                databaseToken = (TSourceToken) arg1;
3263                this.setStartToken(databaseToken);
3264            }else{
3265                schemaToken = (TSourceToken)arg1;
3266                this.setStartToken(schemaToken);
3267                if (schemaToken != null)
3268                   {schemaToken.setDbObjType(TObjectName.ttobjSchemaName);}
3269            }
3270
3271            objectToken = (TSourceToken)arg2;
3272            partToken = (TSourceToken)arg3;
3273            this.setEndToken(partToken);
3274        }
3275    }
3276
3277    public void init(Object arg1, Object arg2, Object arg3, Object arg4)
3278    {
3279        if (arg1 instanceof EDbObjectType){
3280            //this.dbObjectType = (EDbObjectType)arg1;
3281            //init(arg2,arg3,arg4);
3282            initWithThreeTokens((EDbObjectType)arg1,(TSourceToken)arg2,(TSourceToken)arg3,(TSourceToken)arg4);
3283        }else{
3284            numberOfPart = 0;
3285            if (arg1 != null) numberOfPart++;
3286            if (arg2 != null) numberOfPart++;
3287            if (arg3 != null) numberOfPart++;
3288            if (arg4 != null) numberOfPart++;
3289
3290            //serverToken = (TSourceToken)arg1;
3291            databaseToken = (TSourceToken)arg1;
3292            schemaToken = (TSourceToken)arg2;
3293            objectToken = (TSourceToken)arg3;
3294            partToken = (TSourceToken)arg4;
3295            this.setStartToken(databaseToken);
3296            this.setEndToken(partToken);
3297            if (databaseToken != null){
3298                //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
3299                databaseToken.setDbObjectType(EDbObjectType.database);
3300            }else {
3301            }
3302            if (schemaToken != null){
3303                schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
3304            }else {
3305            }
3306        }
3307    }
3308
3309    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5)
3310    {
3311        numberOfPart = 0;
3312        if (arg1 != null) numberOfPart++;
3313        if (arg2 != null) numberOfPart++;
3314        if (arg3 != null) numberOfPart++;
3315        if (arg4 != null) numberOfPart++;
3316        if (arg5 != null) numberOfPart++;
3317
3318        serverToken = (TSourceToken)arg1;
3319        databaseToken = (TSourceToken)arg2;
3320        schemaToken = (TSourceToken)arg3;
3321        objectToken = (TSourceToken)arg4;
3322        partToken = (TSourceToken)arg5;
3323
3324        this.setStartToken(serverToken);
3325        this.setEndToken(partToken);
3326
3327        if (serverToken != null){
3328            //serverToken.setDbObjType(TObjectName.ttobjServerName);
3329            serverToken.setDbObjectType(EDbObjectType.server);
3330        }else{
3331        }
3332        if (databaseToken != null){
3333            //databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
3334            databaseToken.setDbObjectType(EDbObjectType.database);
3335        }else{
3336        }
3337        if (schemaToken != null){
3338            schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
3339        }else{
3340        }
3341    }
3342
3343    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6)
3344    {
3345        numberOfPart = 0;
3346        if (arg1 != null) numberOfPart++;
3347        if (arg2 != null) numberOfPart++;
3348        if (arg3 != null) numberOfPart++;
3349        if (arg4 != null) numberOfPart++;
3350        if (arg5 != null) numberOfPart++;
3351        if (arg6 != null) numberOfPart++;
3352
3353        serverToken = (TSourceToken)arg1;
3354        databaseToken = (TSourceToken)arg2;
3355        schemaToken = (TSourceToken)arg3;
3356        objectToken = (TSourceToken)arg4;
3357        partToken = (TSourceToken)arg5;
3358        propertyToken = (TSourceToken)arg6;
3359
3360        this.setStartToken(serverToken);
3361        this.setEndToken(propertyToken);
3362
3363        if (serverToken != null){
3364            //serverToken.setDbObjType(TObjectName.ttobjServerName);
3365            serverToken.setDbObjectType(EDbObjectType.server);
3366        }else{
3367        }
3368        if (databaseToken != null){
3369           // databaseToken.setDbObjType(TObjectName.ttobjDatabaseName);
3370            databaseToken.setDbObjectType(EDbObjectType.database);
3371        }else{
3372        }
3373        if (schemaToken != null){
3374            schemaToken.setDbObjType(TObjectName.ttobjSchemaName);
3375        }else{
3376        }
3377    }
3378
3379    /**
3380     * Init with 7 tokens for deeply nested struct field access (e.g., BigQuery)
3381     * Pattern: a.b.c.d.e.f.g (7 parts)
3382     */
3383    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7)
3384    {
3385        // First 6 parts use standard tokens
3386        init(arg1, arg2, arg3, arg4, arg5, arg6);
3387
3388        // 7th part goes to additionalParts
3389        if (arg7 != null) {
3390            if (additionalParts == null) {
3391                additionalParts = new java.util.ArrayList<>();
3392            }
3393            additionalParts.add((TSourceToken) arg7);
3394            numberOfPart++;
3395            this.setEndToken((TSourceToken) arg7);
3396        }
3397    }
3398
3399    /**
3400     * Init with 8 tokens for deeply nested struct field access
3401     * Pattern: a.b.c.d.e.f.g.h (8 parts)
3402     */
3403    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8)
3404    {
3405        init(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
3406
3407        if (arg8 != null) {
3408            if (additionalParts == null) {
3409                additionalParts = new java.util.ArrayList<>();
3410            }
3411            additionalParts.add((TSourceToken) arg8);
3412            numberOfPart++;
3413            this.setEndToken((TSourceToken) arg8);
3414        }
3415    }
3416
3417    /**
3418     * Init with 9 tokens for deeply nested struct field access
3419     * Pattern: a.b.c.d.e.f.g.h.i (9 parts)
3420     */
3421    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9)
3422    {
3423        init(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
3424
3425        if (arg9 != null) {
3426            if (additionalParts == null) {
3427                additionalParts = new java.util.ArrayList<>();
3428            }
3429            additionalParts.add((TSourceToken) arg9);
3430            numberOfPart++;
3431            this.setEndToken((TSourceToken) arg9);
3432        }
3433    }
3434
3435    /**
3436     * Init with 10 tokens for deeply nested struct field access
3437     * Pattern: a.b.c.d.e.f.g.h.i.j (10 parts)
3438     */
3439    public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8, Object arg9, Object arg10)
3440    {
3441        init(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
3442
3443        if (arg10 != null) {
3444            if (additionalParts == null) {
3445                additionalParts = new java.util.ArrayList<>();
3446            }
3447            additionalParts.add((TSourceToken) arg10);
3448            numberOfPart++;
3449            this.setEndToken((TSourceToken) arg10);
3450        }
3451    }
3452
3453    /**
3454     * Get additional parts beyond the standard 6 tokens.
3455     * Used for deeply nested struct field access in databases like BigQuery.
3456     *
3457     * @return list of additional source tokens, or null if none
3458     */
3459    public java.util.List<TSourceToken> getAdditionalParts() {
3460        return additionalParts;
3461    }
3462
3463    /**
3464     * The column position of this objectName in the SQL
3465     *
3466     * @return column position
3467     */
3468    @Override
3469    public long getColumnNo() {
3470        long retval = -1;
3471        if (partToken != null) { retval = partToken.columnNo;}
3472        if (objectToken != null) {
3473              retval = objectToken.columnNo;
3474        }
3475        if (schemaToken != null) {
3476              retval = schemaToken.columnNo;
3477        }
3478        if (databaseToken != null) {
3479              retval = databaseToken.columnNo;
3480        }
3481        if (serverToken != null) {
3482              retval = serverToken.columnNo;
3483        }
3484        return retval;
3485    }
3486
3487    /**
3488     * The line number of this objectName in SQL
3489     *
3490     * @return the line number
3491     */
3492    @Override
3493    public long getLineNo() {
3494        long retval = -1;
3495        if (partToken != null) { retval = partToken.lineNo;}
3496        if (objectToken != null) {
3497              retval = objectToken.lineNo;
3498        }
3499        if (schemaToken != null) {
3500              retval = schemaToken.lineNo;
3501        }
3502        if (databaseToken != null) {
3503              retval = databaseToken.lineNo;
3504        }
3505        if (serverToken != null) {
3506              retval = serverToken.lineNo;
3507        }
3508        return retval;
3509    }
3510
3511   private TObjectNameList referencedObjects = null;
3512
3513    public TObjectNameList getReferencedObjects() {
3514        if (referencedObjects == null){
3515            referencedObjects = new TObjectNameList();
3516        }
3517        return referencedObjects;
3518    }
3519
3520    /**
3521     * Returns only the column name if it's prefixed with a table name
3522     *
3523     * @return only the column name if it's prefixed with a table name
3524     */
3525    public String getColumnNameOnly(){
3526
3527        if (getPartToken() == null) return "";
3528        else  return getPartToken().toString();
3529    }
3530
3531    public void accept(TParseTreeVisitor v){
3532        v.preVisit(this);
3533        v.postVisit(this);
3534    }
3535
3536    public void acceptChildren(TParseTreeVisitor v){
3537        v.preVisit(this);
3538        v.postVisit(this);
3539    }
3540
3541//    private TSourceToken sortType = null;
3542//
3543//    public void setSortType(TSourceToken sortType) {
3544//        this.sortType = sortType;
3545//    }
3546//
3547//    /**
3548//     * When this object is column in primary key(column,...), unique key(column,...) in sql server
3549//     * there maybe sort information like column asc, column desc
3550//     * this token represents for ASC, DESC if specified.
3551//     *
3552//     * @return ASC, DESC or null
3553//     */
3554//
3555//    public TSourceToken getSortType() {
3556//        return sortType;
3557//    }
3558
3559    /**
3560     * It's the same as {@link #getPartToken} if {@link #getDbObjectType} is {@link gudusoft.gsqlparser.EDbObjectType#column}
3561     *
3562     * @return source token that represents column, return null if this objectName is not type of column
3563     *
3564     */
3565    public TSourceToken getColumnToken(){
3566        TSourceToken ret = null;
3567        if (this.getObjectType() == ttobjColumn){
3568            ret = this.getPartToken();
3569        }
3570        return ret;
3571    }
3572
3573    /*
3574     *  re-arranage objectname to make it a valid name includes attribute name
3575      * used by teradata yacc file only.
3576      * valid syntax:
3577       * column.attr1().attr2().
3578       * column.attr1().attr2().attr3()
3579       * table.column.attr1().attr2().
3580       * table.column.attr1().attr2().attr3()
3581       *
3582     *   @return
3583     */
3584   public boolean isAttributeNameInObjectName(TSourceToken leftparen,TSourceToken rightparen){
3585        boolean ret = false;
3586        if ((this.partToken == null) || (this.objectToken == null)){
3587            return ret;
3588        }
3589        this.objectType = TObjectName.ttobjColumn;
3590        this.columnAttributes = new TObjectNameList();
3591        TObjectName attr1 = new TObjectName();
3592        attr1.objectType = TObjectName.ttobjAttribute;
3593        attr1.init(this.partToken);
3594        attr1.setEndToken(rightparen);
3595        this.columnAttributes.addObjectName(attr1);
3596
3597        this.partToken = this.objectToken;
3598
3599        if (this.schemaToken != null){
3600            this.objectToken = this.schemaToken;
3601        }
3602
3603        return true;
3604   }
3605
3606    /**
3607     * Used internally in hive .y file to merge two objectNames
3608     */
3609    public void mergeObjectName(TObjectName objectName){
3610        this.objectToken = this.partToken;
3611        this.partToken = objectName.getPartToken();
3612        this.setStartToken(objectToken);
3613        this.setEndToken(partToken);
3614    }
3615
3616    public void mergeObjectName(TObjectName objectName,TObjectName objectName2){
3617        this.schemaToken = this.partToken;
3618        this.objectToken = objectName.getPartToken();
3619        this.partToken = objectName2.getPartToken();
3620        this.setStartToken(schemaToken);
3621        this.setEndToken(partToken);
3622    }
3623
3624
3625    public void columnToProperty(){
3626        // if (numberOfPart == 1) return;
3627        if (this.pseudoTableType != EPseudoTableType.none) return; // pseudo table tokens already in correct position
3628        if (this.propertyToken != null) return; // columnToProperty() already called
3629        if (! ((this.partToken != null) && (this.objectToken != null))) return; // 既然是 column.property , 那么 partToken and objectToken 不能为空
3630
3631        this.propertyToken = this.partToken;
3632        this.partToken = this.objectToken;
3633        this.objectToken = this.schemaToken;
3634
3635        this.setDbObjectTypeDirectly(EDbObjectType.column);
3636    }
3637
3638    public void appendObjectName(TObjectName objectName){
3639        if (this.databaseToken != null){
3640            this.serverToken = this.databaseToken;
3641        }
3642        if (this.schemaToken != null){
3643            this.databaseToken = this.schemaToken;
3644        }
3645        if (this.objectToken != null){
3646            this.schemaToken = this.objectToken;
3647        }
3648        this.objectToken = this.partToken;
3649        this.partToken = objectName.getPartToken();
3650        this.setEndTokenDirectly(this.partToken);
3651    }
3652
3653    private TSourceToken commentString;
3654
3655    public void setCommentString(TSourceToken commentString) {
3656        this.commentString = commentString;
3657    }
3658
3659    public TSourceToken getCommentString() {
3660
3661        return commentString;
3662    }
3663
3664
3665    /**
3666     * The X and Y position of this objectName in the SQL
3667     *
3668     * @return coordinate in string text
3669     */
3670    public String coordinate(){
3671        return this.getStartToken().lineNo+","+this.getEndToken().columnNo;
3672    }
3673
3674
3675    /**
3676     * @deprecated replaced by {@link EDbObjectType}.
3677     *
3678     * this is not an object, like sysdate function in oracle database
3679     */
3680    public final static int ttobjNotAObject = -1;
3681
3682    /**
3683     * @deprecated replaced by {@link EDbObjectType}.
3684     * object type can't be determined.
3685     */
3686    public final static int ttobjUnknown = 0;
3687
3688    /**
3689     * @deprecated replaced by {@link EDbObjectType}.
3690     * column in table, objectToken is table if specified, and partToken is column name.
3691     */
3692    public final static int ttobjColumn = 1;
3693
3694    /**
3695     * @deprecated replaced by {@link EDbObjectType}.
3696     * column alias in objectToken.
3697     */
3698    public final static int ttobjColumnAlias = 2;
3699
3700    /**
3701     * @deprecated replaced by {@link EDbObjectType}.
3702     * table name in objectToken.
3703     */
3704    public final static int ttobjTable = 3;
3705
3706
3707    /**
3708     * @deprecated replaced by {@link EDbObjectType}.
3709     * parameter name in objectToken.
3710     */
3711    public final static int ttobjParameter = 9;
3712
3713    /**
3714     * @deprecated replaced by {@link EDbObjectType}.
3715     * variable name in objectToken.
3716     */
3717    public final static int ttobjVariable = 10;
3718
3719
3720    /**
3721     * @deprecated replaced by {@link EDbObjectType#method}.
3722     *  column method like SetXY below, column method in {@link #methodToken}, and colomn name in {@link #partToken}.
3723     *<p>   UPDATE Cities
3724     *<p>   SET Location.SetXY(23.5, 23.5)
3725     *
3726     *
3727     */
3728    public final static int ttobjColumnMethod = 11;
3729
3730    /**
3731     * Named argument parameter name in function calls.
3732     * <p>Example: In Snowflake FLATTEN(INPUT => parse_json(col), outer => TRUE),
3733     * "INPUT" and "outer" are named argument parameter names, NOT column references.
3734     * <p>These should be skipped during column resolution and data lineage analysis.
3735     */
3736    public final static int ttobjNamedArgParameter = 12;
3737
3738    /**
3739     * @deprecated replaced by {@link EDbObjectType}.
3740     * function name in {@link #objectToken}
3741     */
3742    public final static int ttobjFunctionName = 13;
3743
3744
3745    /**
3746     * @deprecated replaced by {@link EDbObjectType#constraint}.
3747     * constraint name in {@link #objectToken}
3748     */
3749    public final static int ttobjConstraintName = 19;
3750
3751    /**
3752     * @deprecated replaced by {@link EDbObjectType}.
3753     * string constant in {@link #objectToken}
3754     */
3755    public final static int ttobjStringConstant = 23;
3756
3757
3758    /**
3759     * @deprecated replaced by {@link EDbObjectType}.
3760     * attribute name is in {@link #partToken}
3761     */
3762    public final static int ttobjAttribute = 26;
3763
3764
3765    /**
3766     * @deprecated replaced by {@link EDbObjectType}.
3767     * datatype was not represented by a TObjectName object, this constant was used in source tokens that consist of  TTypeName.
3768     */
3769    public final static int ttobjDatatype = 30;
3770
3771    /**
3772     * @deprecated replaced by {@link EDbObjectType}.
3773     *  schema name in {@link #schemaToken}
3774     */
3775    public final static int ttobjSchemaName = 31;
3776
3777
3778    /**
3779     * @deprecated replaced by {@link EDbObjectType}.
3780     * postgresql
3781     * Positional Parameters, $1, $1[1], $1[1,10]
3782     * parameter name is in {@link #partToken} of $1,
3783     * and parameter name is in {@link #objectToken} of $1.columnName,
3784     * and column name is in {@link #partToken}
3785     */
3786
3787    public final static int ttobjPositionalParameters = 61;
3788
3789
3790
3791    private void setColumnTokenOfPositionalParameters(TSourceToken column){
3792        this.objectToken = this.partToken;
3793        this.partToken = column;
3794    }
3795
3796    private int objectType = ttobjUnknown;
3797
3798
3799    /**
3800     * @deprecated replaced by {@link EDbObjectType}.
3801     * this type is used in TObjectNameList, when objects in TObjectNameList includes more than
3802     * one type, objtype of that TObjectNameList was set to ttobjMixed.
3803     *
3804     * removed since v2.9.2.5
3805     */
3806   // public final static int ttobjMixed = 100;
3807
3808    /**
3809     * @deprecated replaced by {@link EDbObjectType#library}.
3810     * removed since v2.9.2.5
3811     */
3812   // public final static  int ttObjLibrary = 72;
3813
3814    /**
3815     * @deprecated replaced by {@link EDbObjectType#oracleHint}.
3816     * removed since v2.9.2.5
3817     */
3818   // public final static  int ttObjOracleHint = 70;
3819
3820    /**
3821     * @deprecated replaced by {@link EDbObjectType#fieldName}.
3822     * check {@link gudusoft.gsqlparser.nodes.TExpression#getFieldName()} for more
3823     * removed since v2.9.2.5
3824     */
3825   // public final static int ttobjFieldName = 51;
3826
3827    /**
3828     * @deprecated replaced by {@link EDbObjectType#miningModel}.
3829     * removed since v2.9.2.5
3830     */
3831    // public final static int ttobjMiningModel = 46;
3832
3833    /**
3834     * @deprecated replaced by {@link EDbObjectType#materializedView}.
3835     * removed since v2.9.2.5
3836     */
3837    // public final static int ttobjMaterializedView = 44;
3838
3839    /**
3840     * @deprecated replaced by {@link EDbObjectType#indextype}.
3841     * removed since v2.9.2.5
3842     */
3843   // public final static int ttobjIndexType = 42;
3844
3845    /**
3846     * @deprecated replaced by {@link EDbObjectType#operator}.
3847     * removed since v2.9.2.5
3848     */
3849    // public final static int ttobjOperator = 40;
3850
3851    /**
3852     * @deprecated replaced by {@link EDbObjectType#server}.
3853     * server name in {@link #serverToken}
3854     *
3855     * removed since v2.9.2.5
3856     */
3857  //  public final static int ttobjServerName = 32;
3858
3859    /**
3860     * @deprecated replaced by {@link EDbObjectType#sequence}.
3861     * Sequence name in {@link #objectToken}
3862     *
3863     * removed since v2.9.2.5
3864     */
3865   // public final static int ttobjSequence = 29;
3866
3867    /**
3868     * @deprecated replaced by {@link EDbObjectType#plsql_package}.
3869     * package name in {@link #objectToken}
3870     *
3871     * removed since v2.9.2.5
3872     */
3873   // public final static int ttobjPackage = 28;
3874
3875    /**
3876     * @deprecated replaced by {@link EDbObjectType#alias}.
3877     * alias name in {@link #objectToken}
3878     *
3879     * removed since v2.9.2.5
3880     */
3881   // public final static int ttobjAliasName = 25;
3882
3883
3884    /**
3885     * @deprecated replaced by {@link EDbObjectType#trigger}.
3886     * Trigger name in {@link #objectToken}
3887     *
3888     * removed since v2.9.2.5
3889     */
3890   // public final static int ttobjTrigger = 24;
3891
3892    /**
3893     * @deprecated replaced by {@link EDbObjectType#database}.
3894     * Database name in {@link #objectToken}
3895     *
3896     * removed since v2.9.2.5
3897     */
3898   // public final static int ttobjDatabaseName = 22;
3899
3900    /**
3901     * @deprecated replaced by {@link EDbObjectType#transaction}.
3902     * Transaction name in {@link #objectToken}
3903     *
3904     * removed since v2.9.2.5
3905     */
3906    // public final static int ttobjTransactionName = 21;
3907
3908
3909    /**
3910     * @deprecated replaced by {@link EDbObjectType#user_defined_type}.
3911     * type name in {@link #objectToken}
3912     *
3913     * removed since v2.9.2.5
3914     */
3915   // public final static int ttobjTypeName = 27;
3916
3917    /**
3918     * @deprecated replaced by {@link EDbObjectType#property}.
3919     * property name in {@link #propertyToken}
3920     *
3921     * removed since v2.9.2.5
3922     */
3923   // public final static int ttobjPropertyName = 20;
3924
3925    /**
3926     * @deprecated replaced by {@link EDbObjectType#view}.
3927     * view name in {@link #objectToken}
3928     *
3929     * removed since v2.9.2.5
3930     */
3931   // public final static int ttobjViewName = 18;
3932
3933    /**
3934     * @deprecated replaced by {@link EDbObjectType#cursor}.
3935     * cursor name in {@link #objectToken}
3936     *
3937     * removed since v2.9.2.5
3938     */
3939  //  public final static int ttobjCursorName = 17;
3940
3941    /**
3942     * @deprecated replaced by {@link EDbObjectType#materializedView}.
3943     * materialized view name in {@link #objectToken}
3944     *
3945     * removed since v2.9.2.5
3946     */
3947   // public final static int ttobjMaterializedViewName = 16;
3948
3949    /**
3950     * @deprecated replaced by {@link EDbObjectType#index}.
3951     * index name in {@link #objectToken}
3952     *
3953     * removed since v2.9.2.5
3954     */
3955   // public final static int ttobjIndexName = 15;
3956
3957    /**
3958     * @deprecated replaced by {@link EDbObjectType#label}.
3959     * label name in {@link #objectToken}
3960     *
3961     * removed since v2.9.2.5
3962     */
3963   // public final static int ttobjLabelName = 14;
3964
3965    /**
3966     * @deprecated replaced by {@link EDbObjectType#procedure}.
3967     * procedure name in {@link #objectToken}
3968     *
3969     * removed since v2.9.2.5
3970     */
3971   // public final static int ttobjProcedureName = 12;
3972
3973    /**
3974     * @deprecated replaced by {@link EDbObjectType#variable}.
3975     * table variable in objectToken.
3976     *
3977     * removed since v2.9.2.5
3978     */
3979   // public final static int ttobjTableVar = 8;
3980
3981    /**
3982     * @deprecated replaced by {@link EDbObjectType#cte}.
3983     * table name in objectToken.
3984     *
3985     * removed since v2.9.2.5
3986     */
3987    // public final static int ttobjTableCTE = 5;
3988
3989    /**
3990     * @deprecated replaced by {@link EDbObjectType}.
3991     * table name in objectToken.
3992     */
3993    // public final static int ttobjTableTemp = 6;
3994
3995    /**
3996     * @deprecated replaced by {@link EDbObjectType}.
3997     */
3998    //  public final static int ttobjTablePivot = 7;
3999
4000    /**
4001     * @deprecated replaced by {@link EDbObjectType#table_alias}.
4002     * table alias in objectToken
4003     *
4004     * removed since v2.9.2.5
4005     */
4006   // public final static int ttObjTableAlias = 4;
4007}