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