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