001package gudusoft.gsqlparser;
002
003
004import gudusoft.gsqlparser.compiler.*;
005import gudusoft.gsqlparser.nodes.*;
006import gudusoft.gsqlparser.nodes.dax.TDaxFunction;
007import gudusoft.gsqlparser.nodes.teradata.THashByClause;
008import gudusoft.gsqlparser.sqlenv.TSQLEnv;
009import gudusoft.gsqlparser.sqlenv.TSQLFunction;
010import gudusoft.gsqlparser.sqlenv.TSQLTable;
011import gudusoft.gsqlparser.resolver2.metadata.ParseTimeCatalog;
012import gudusoft.gsqlparser.stmt.*;
013import gudusoft.gsqlparser.stmt.dax.TDaxStmt;
014import gudusoft.gsqlparser.stmt.oracle.*;
015import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
016import gudusoft.gsqlparser.util.OraclePseudoColumnUtil;
017import gudusoft.gsqlparser.util.SQLUtil;
018import gudusoft.gsqlparser.util.functionChecker;
019import gudusoft.gsqlparser.util.keywordChecker;
020
021import java.util.ArrayDeque;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Deque;
025import java.util.List;
026import java.util.Stack;
027import java.util.TreeMap;
028import java.security.MessageDigest;
029import java.security.NoSuchAlgorithmException;
030import java.nio.charset.StandardCharsets;
031
032/**
033 * TCustomSqlStatement is the root class for all SQL statements.
034 */
035public class TCustomSqlStatement extends TParseTreeNode implements IRelation{
036
037    private String sqlHash;
038
039    public void setSqlHash(String sqlHash) {
040        this.sqlHash = sqlHash;
041    }
042
043    /**
044     * Returns a stable, vendor-aware hash of this statement's SQL text for
045     * lineage grouping and statement identity.
046     * <p>
047     * Purpose:
048     * <ul>
049     *     <li>Provide a deterministic identifier for a statement that is
050     *     insensitive to formatting (whitespace, comments, keyword case).</li>
051     *     <li>Act as the first component of a recommended {@code statementKey}
052     *     for lineage grouping: {@code statementKey = sqlHash + "#" + queryId}.</li>
053     * </ul>
054     * How it works:
055     * <ul>
056     *     <li>Builds a normalized textual representation of the statement by
057     *     iterating the token chain between {@code getStartToken()} and
058     *     {@code getEndToken()}.</li>
059     *     <li>Normalization rules (Profile A by default): remove comments;
060     *     collapse spacing deterministically; uppercase keywords; handle
061     *     identifiers by the current vendor's case-sensitivity
062     *     ({@link gudusoft.gsqlparser.sqlenv.TSQLEnv#columnCollationCaseSensitive}).
063     *     For delimited identifiers (quoted identifiers), the quotes are
064     *     removed via {@link gudusoft.gsqlparser.TBaseType#removeQuoteChar(String)}
065     *     before case normalization. String literals are kept as-is.</li>
066     *     <li>The hash input is prefixed by a normalization version and the
067     *     current vendor, so future evolution of the normalizer will not break
068     *     previously computed values: {@code normVersion + "\n" + vendor + "\n" + normalizedSql}.</li>
069     *     <li>Hash function: SHA-256 (lowercase hex).</li>
070     * </ul>
071     * Usage:
072     * <ul>
073     *     <li>Call {@code getSqlHash(false)} for cached result or lazy
074     *     calculation with Profile A (identity-safe) normalization.</li>
075     *     <li>Call {@code getSqlHash(true)} to force recalculation (e.g., after
076     *     mutating the underlying token chain).</li>
077     *     <li>If you need a grouping-friendly variant (masking certain literal
078     *     classes), use {@link #computeSqlHash(SqlNormalizationProfile, String)}
079     *     with {@link SqlNormalizationProfile#GROUPING_FRIENDLY}.</li>
080     * </ul>
081     *
082     * @param forceReCalculate if true, recompute the hash even if a cached
083     *                         value exists
084     * @return lowercase hex SHA-256 hash of the normalized SQL text
085     */
086    public String getSqlHash(boolean forceReCalculate) {
087        if (sqlHash != null && !forceReCalculate) {
088            return sqlHash;
089        }
090        // Default: Profile A (identity-safe)
091        this.sqlHash = computeSqlHash(SqlNormalizationProfile.IDENTITY_SAFE, DEFAULT_SQLHASH_NORM_VERSION);
092        return this.sqlHash;
093    }
094
095    /**
096     * Backward compatible overload equivalent to {@code getSqlHash(false)}.
097     */
098    public String getSqlHash() {
099        return getSqlHash(false);
100    }
101
102    /**
103     * Normalization profiles for SQL hashing.
104     * <ul>
105     *     <li>IDENTITY_SAFE: Remove comments and normalize spacing/case only.
106     *     Literal values are preserved. Operator synonyms are minimally
107     *     unified (e.g., {@code !=} to {@code <>}).</li>
108     *     <li>GROUPING_FRIENDLY: In addition to IDENTITY_SAFE, date/time
109     *     string literals are normalized to {@code '1970-01-01'} to aid
110     *     grouping across different runs where only timestamps vary.</li>
111     * </ul>
112     */
113    public enum SqlNormalizationProfile {
114        IDENTITY_SAFE,
115        GROUPING_FRIENDLY
116    }
117
118    private static final String DEFAULT_SQLHASH_NORM_VERSION = "sqlHash.norm.v1";
119
120    /**
121     * Compute a SQL hash using the given normalization profile and version.
122     * See {@link #getSqlHash(boolean)} for details.
123     *
124     * @param profile     normalization profile
125     * @param normVersion version tag embedded in the hash input
126     * @return lowercase hex SHA-256 hash
127     */
128    public String computeSqlHash(SqlNormalizationProfile profile, String normVersion) {
129        String normalized = toNormalizedSql(profile);
130        String input = normVersion + "\n" + String.valueOf(this.dbvendor) + "\n" + normalized;
131        return sha256Hex(input);
132    }
133
134    /**
135     * Produce a normalized textual representation of this statement according
136     * to the supplied profile. The method is non-mutating: it does not alter
137     * token texts or statuses permanently.
138     *
139     * Rules applied:
140     * - Remove comments.
141     * - Remove trailing semicolon.
142     * - Deterministic spacing around punctuation/operators.
143     * - Uppercase keywords, keep string literals as-is.
144     * - Identifiers: if the vendor's column collation is case sensitive, keep
145     *   identifier case; otherwise uppercase. For quoted identifiers, remove
146     *   quoting via {@link TBaseType#removeQuoteChar(String)} prior to case
147     *   handling.
148     * - Minimal operator unification: {@code "!="} becomes {@code "<>"}.
149     * - Profile B adds date/time string masking: {@code '1970-01-01'}.
150     *
151     * @param profile normalization profile
152     * @return normalized SQL string
153     */
154    public String toNormalizedSql(SqlNormalizationProfile profile) {
155        TSourceToken start = getStartToken();
156        TSourceToken end = getEndToken();
157        if (start == null || end == null) {
158            return String.valueOf(this.toString());
159        }
160
161        boolean idCaseSensitive = Boolean.TRUE.equals(TSQLEnv.columnCollationCaseSensitive.get(this.dbvendor));
162
163        StringBuilder sb = new StringBuilder(256);
164        TokenClass prevClass = null;
165        char lastAppended = '\0';
166
167        // We may need to skip a single trailing semicolon that is the end token
168        // Detect once to simplify checks in the loop
169        boolean endIsSemicolon = (end.toString().equals(";"));
170
171        TSourceToken cur = start;
172        while (cur != null) {
173            // Skip tokens that should not contribute
174            if (isComment(cur)) {
175                // skip comments
176            } else if (cur == end && endIsSemicolon) {
177                // drop trailing semicolon
178            } else if (cur.tokenstatus == ETokenStatus.tsdeleted || cur.tokenstatus == ETokenStatus.tsignorebyyacc) {
179                // ignore deleted/ignored tokens
180            } else {
181                String text = normalizeTokenText(cur, idCaseSensitive, profile);
182                if (text != null && !text.isEmpty()) {
183                    TokenClass clazz = classify(cur, text);
184                    if (shouldAddSpaceBefore(prevClass, clazz, lastAppended)) {
185                        sb.append(' ');
186                        lastAppended = ' ';
187                    }
188                    sb.append(text);
189                    lastAppended = text.charAt(text.length() - 1);
190                    prevClass = clazz;
191                }
192            }
193
194            if (cur == end) {
195                break;
196            } else {
197                cur = cur.getNextTokenInChain();
198            }
199        }
200
201        // Trim any trailing single space for cleanliness
202        int len = sb.length();
203        if (len > 0 && sb.charAt(len - 1) == ' ') {
204            sb.setLength(len - 1);
205        }
206        return sb.toString();
207    }
208
209    private static boolean isComment(TSourceToken t) {
210        return t.tokentype == ETokenType.ttsimplecomment || t.tokentype == ETokenType.ttbracketedcomment;
211    }
212
213    private enum TokenClass { WORD, OP, DOT, COMMA, PAREN_LEFT, PAREN_RIGHT, SEMICOLON, OTHER }
214
215    private static TokenClass classify(TSourceToken t, String normalizedText) {
216        switch (t.tokentype) {
217            case ttidentifier:
218            case ttdqstring:
219            case ttdbstring:
220            case ttbrstring:
221            case ttnumber:
222            case ttsqstring:
223            case ttkeyword:
224            case ttnonreservedkeyword:
225            case ttbindvar:
226            case ttsqlvar:
227            case ttsubstitutionvar:
228                return TokenClass.WORD;
229            case ttperiod:
230                return TokenClass.DOT;
231            case ttcomma:
232                return TokenClass.COMMA;
233            case ttleftparenthesis:
234                return TokenClass.PAREN_LEFT;
235            case ttrightparenthesis:
236                return TokenClass.PAREN_RIGHT;
237            case ttsemicolon:
238            case ttsemicolon2:
239            case ttsemicolon3:
240                return TokenClass.SEMICOLON;
241            case ttequals:
242            case ttplussign:
243            case ttminussign:
244            case ttasterisk:
245            case ttslash:
246            case ttgreaterthan:
247            case ttlessthan:
248            case ttsinglecharoperator:
249            case ttmulticharoperator:
250            case ttconcatenationop:
251                return TokenClass.OP;
252            default:
253                break;
254        }
255        // Heuristic: treat UNKNOWN single-char punctuation as operator
256        if (normalizedText.length() == 1 && !Character.isLetterOrDigit(normalizedText.charAt(0))) {
257            return TokenClass.OP;
258        }
259        return TokenClass.OTHER;
260    }
261
262    private static boolean shouldAddSpaceBefore(TokenClass prev, TokenClass curr, char lastAppended) {
263        if (prev == null) return false;
264        if (lastAppended == '\0') return false;
265
266        // No space rules
267        if (curr == TokenClass.DOT || curr == TokenClass.COMMA || curr == TokenClass.PAREN_RIGHT) return false;
268        if (prev == TokenClass.DOT || prev == TokenClass.PAREN_LEFT) return false;
269
270        // Space around operators and between words
271        if (prev == TokenClass.OP && (curr == TokenClass.WORD || curr == TokenClass.PAREN_LEFT)) return true;
272        if ((prev == TokenClass.WORD || prev == TokenClass.PAREN_RIGHT) && (curr == TokenClass.OP || curr == TokenClass.WORD)) return true;
273
274        // After comma ensure a space before next word
275        if (prev == TokenClass.COMMA && (curr == TokenClass.WORD || curr == TokenClass.PAREN_LEFT)) return true;
276
277        // Default: no space
278        return false;
279    }
280
281    private String normalizeTokenText(TSourceToken t, boolean idCaseSensitive, SqlNormalizationProfile profile) {
282        String s = t.toString();
283
284        // Minimal operator unification: != => <>
285        if (t.tokentype == ETokenType.ttmulticharoperator || t.tokentype == ETokenType.ttsinglecharoperator ||
286            t.tokentype == ETokenType.ttgreaterthan || t.tokentype == ETokenType.ttlessthan) {
287            if ("!=".equals(s)) {
288                return "<>";
289            }
290            return s;
291        }
292
293        switch (t.tokentype) {
294            case ttsimplecomment:
295            case ttbracketedcomment:
296                return null; // removed
297            case ttkeyword:
298            case ttnonreservedkeyword:
299                return s.toUpperCase(java.util.Locale.ROOT);
300            case ttidentifier: {
301                // regular identifier
302                return idCaseSensitive ? s : s.toUpperCase(java.util.Locale.ROOT);
303            }
304            case ttdqstring:
305            case ttdbstring:
306            case ttbrstring: {
307                // delimited/quoted identifier -> remove quotes, then apply case rule
308                String unquoted = TBaseType.removeQuoteChar(s);
309                return idCaseSensitive ? unquoted : unquoted.toUpperCase(java.util.Locale.ROOT);
310            }
311            case ttsqstring: {
312                if (profile == SqlNormalizationProfile.GROUPING_FRIENDLY && looksLikeDateOrTimestampLiteral(s)) {
313                    return "'1970-01-01'";
314                }
315                return s; // keep string literal as-is
316            }
317            default:
318                return s;
319        }
320    }
321
322    private static boolean looksLikeDateOrTimestampLiteral(String s) {
323        // Very lightweight check for common SQL string date/timestamp formats, e.g. '2025-09-14' or '2025-09-14 12:34:56'
324        // Input includes surrounding quotes per token text.
325        if (s == null || s.length() < 2) return false;
326        if (!(s.charAt(0) == '\'' && s.charAt(s.length() - 1) == '\'')) return false;
327        String inner = s.substring(1, s.length() - 1).trim();
328        // yyyy-mm-dd or yyyy-mm-dd hh:mm[:ss[.fff]]
329        if (inner.matches("\\d{4}-\\d{2}-\\d{2}")) return true;
330        if (inner.matches("\\d{4}-\\d{2}-\\d{2}[ T]\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?")) return true;
331        return false;
332    }
333
334    private static String sha256Hex(String input) {
335        try {
336            MessageDigest md = MessageDigest.getInstance("SHA-256");
337            byte[] out = md.digest(input.getBytes(StandardCharsets.UTF_8));
338            char[] hex = new char[out.length * 2];
339            final char[] digits = "0123456789abcdef".toCharArray();
340            for (int i = 0, j = 0; i < out.length; i++) {
341                int b = out[i] & 0xFF;
342                hex[j++] = digits[(b >>> 4) & 0x0F];
343                hex[j++] = digits[b & 0x0F];
344            }
345            return new String(hex);
346        } catch (NoSuchAlgorithmException e) {
347            // Should never happen on a standard JVM
348            throw new RuntimeException("SHA-256 not available", e);
349        }
350    }
351
352    private String queryId;
353
354    public void setQueryId(String queryId) {
355        this.queryId = queryId;
356    }
357
358    /**
359     * Retrieves the unique and stable identifier for this SQL statement.
360     * <p>
361     * The queryId provides a reliable way to reference any statement, including subqueries,
362     * within a parsed SQL script. It is generated hierarchically based on the statement's
363     * position within the Abstract Syntax Tree (AST), ensuring that the ID is reproducible
364     * across identical SQL inputs.
365     * <p>
366     * <b>ID Format:</b>
367     * <ul>
368     *     <li>A top-level statement has an ID like {@code "stmt_0_select"}, where {@code 0} is the index
369     *         of the statement in the script and {@code select} is the statement type.</li>
370     *     <li>A nested statement will have a path-like ID that includes its parent's ID. For example,
371     *         an {@code INSERT} statement containing a {@code SELECT} subquery might have an ID for the
372     *         subquery like {@code "stmt_0_insert#stmt_1_select"}.</li>
373     * </ul>
374     * This identifier is particularly useful for tasks like data lineage analysis, where tracking
375     * the origin and transformation of data through various statements is required.
376     *
377     * @return The unique query identifier string for this statement, or {@code null} if it has not been set.
378     */
379    public String getQueryId() {
380        return queryId;
381    }
382
383    public void setUsingVariableList(TColumnDefinitionList usingVariableList) {
384        this.usingVariableList = usingVariableList;
385    }
386
387    private TColumnDefinitionList usingVariableList;
388
389    /*
390    * Variables defined in teradata using clause.
391    * */
392    public TColumnDefinitionList getUsingVariableList() {
393        return usingVariableList;
394    }
395
396    protected ArrayList<TAttributeNode> relationAttributes = new ArrayList<>();
397
398    @Override
399    public ArrayList<TAttributeNode> getAttributes(){
400       // if (relationAttributes.size() != 0) return relationAttributes;
401        relationAttributes.clear();
402        for(TTable table:relations){
403            //relationAttributes.addAll(table.getAttributes());
404            TAttributeNode.addAllNodesToList(table.getAttributes(),relationAttributes);
405        }
406
407        return relationAttributes;
408    }
409
410    @Override
411    public String getRelationName(){
412        return null;
413    }
414
415    @Override
416    public int size(){
417        return relationAttributes.size();
418    }
419
420    @Override
421    public String toScript(){
422        String ret = super.toScript();
423        if ((ret == null)||(ret.isEmpty())){
424            ret = this.toString();
425        }else{
426            if ((this.getEndToken() != null) && (this.getEndToken().tokencode == ';')) {
427                if (!ret.endsWith(";")){
428                    ret = ret + ";";
429                }
430            }
431        }
432        return ret;
433    }
434
435    protected TFromClause fromClause;
436
437    public void setFromClause(TFromClause fromClause) {
438        this.fromClause = fromClause;
439    }
440
441    public TFromClause getFromClause() {
442        return fromClause;
443    }
444
445    /**
446     * Relations that used in from clause of select statement.
447     * Or tables of other statements such as insert, update, delete and etc
448     *
449     * Please use this property to get the relations instead of {@link #getTables()} and {@link #getJoins()}after version 2.7.4.0
450     *
451     * when a join is used in from clause, then the table in getRelations() is type of ETableSource.join, and you can
452     * use TTable.getJoinExpr() to get this join.
453     *
454     * @return
455     */
456    public ArrayList<TTable> getRelations() {
457        return relations;
458    }
459
460    private ArrayList<TTable> relations = new ArrayList<>();
461
462    protected TTable fromSourceTable;
463
464    /**
465     * This is table in from clause if only one table is listed in the from clause,
466     * If more than one table is listed in from clause, please check {@link #getFromSourceJoin()} instead.
467     *
468     * @return table in from clause
469     */
470    public TTable getFromSourceTable() {
471        return fromSourceTable;
472    }
473
474    /**
475     * This is a join in from clause, including left and right relation.
476     * If only a single table is listed in from clause, please use {@link #getFromSourceTable()} instead
477     * @return
478     */
479    public TJoinExpr getFromSourceJoin() {
480        return fromSourceJoin;
481    }
482
483    protected TJoinExpr fromSourceJoin;
484
485    private String asCanonicalText = null;
486
487    /**
488     *  this method return a canonical form of a SQL statement in plan text.
489     *  <br>1. remove all comment inside SQL query.
490     *  <br>2. remove redundant parenthesis at the begin/end of a select statement.
491     *  <br>3. replace all number in where clause with 999 constant
492     *  <br>4. replace all string constant in where clause with 'placeholder_str'
493     *  <br>5. all number elements in a list such as (1,2,3,4) will be change to a single element (999)
494     *  <br>6. all string elements in a list such as ('a','b','c','d') will be change to a single element ('placeholder_str')
495     *
496     * @return a canonical form of a SQL statement in plan text.
497     */
498    public String asCanonical(){
499        if (asCanonicalText != null) return asCanonicalText;
500
501        String ret = null;
502        TSourceToken lcStartToken = getStartToken();
503        if (lcStartToken ==  null) return toString();
504        TSourceToken lcEndToken = getEndToken();
505        if (lcEndToken ==  null) return toString();
506
507        // remove the ; token at the end of statement
508        if (lcEndToken.tokencode == ';') lcEndToken.tokenstatus = ETokenStatus.tsdeleted;
509
510
511        // remove ( ) at the begin and end of the statement
512        TSourceToken lcCurrentToken = lcStartToken;
513        while (lcCurrentToken != null){
514            if (lcCurrentToken.tokencode != '(') {
515                break;
516            }else{
517                if (lcCurrentToken.getLinkToken() != null){
518                    lcCurrentToken.tokenstatus  = ETokenStatus.tsdeleted;
519                    lcCurrentToken.getLinkToken().tokenstatus  = ETokenStatus.tsdeleted;
520                }
521            }
522
523            if (lcCurrentToken.equals(lcEndToken)){
524                break;
525            }else{
526                lcCurrentToken = lcCurrentToken.getNextTokenInChain();
527            }
528        }
529
530        // change constant to placeholder, all number change to 999 and string constant change to placeholder_str
531        constantVisitor cv = new constantVisitor();
532        this.acceptChildren(cv);
533
534
535        boolean chainUnchanged = true, includingComment = false;
536        StringBuffer sb = new StringBuffer("");
537        TSourceToken lcPrevSt = null;
538        boolean ignoreNextReturnToken = false, isChainModified = false;
539
540        lcCurrentToken = lcStartToken;
541        while (lcCurrentToken != null){
542            if((lcCurrentToken.tokenstatus == ETokenStatus.tsdeleted)
543                    ||(!includingComment  && ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) ||(lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen)))
544            ){
545                // ignore this token, do nothing
546                //System.out.println("out: ignore deleted token:"+lcCurrentToken.astext);
547            }else{
548                //
549                sb.append(lcCurrentToken.toString());
550                if (lcCurrentToken.isChangedInAsCanonical()){
551                    lcCurrentToken.restoreText();
552                }
553            }
554
555            if (lcCurrentToken.equals(lcEndToken)){
556                break;
557            }else{
558                lcCurrentToken = lcCurrentToken.getNextTokenInChain();
559            }
560
561        }
562        asCanonicalText = sb.toString();
563        return asCanonicalText;
564    }
565
566    private TCTE cteIncludeThisStmt = null;
567
568    public void setCteIncludeThisStmt(TCTE cteIncludeThisStmt) {
569        this.cteIncludeThisStmt = cteIncludeThisStmt;
570    }
571
572    public TCTE getCteIncludeThisStmt() {
573        return cteIncludeThisStmt;
574    }
575
576    private TreeMap<String,TResultColumn> expandedResultColumns = null;
577
578    public TreeMap<String,TResultColumn> getExpandedResultColumns() {
579        if (expandedResultColumns == null){
580            expandedResultColumns = new TreeMap<>();
581        }
582        return expandedResultColumns;
583    }
584
585    public TSQLFunction searchFunctionInSQLEnv(String functionName){
586        if (getSqlEnv() == null) return null;
587        return getSqlEnv().searchFunction(functionName);
588    }
589
590    public TSQLEnv getSqlEnv() {
591        if (getGlobalScope() == null) return null;
592        return getGlobalScope().getSqlEnv();
593    }
594
595    public TGlobalScope getGlobalScope() {
596        TGlobalScope lcResult = null;
597        if (frameStack != null){
598            if (frameStack.get(0) != null){
599                lcResult = (TGlobalScope)frameStack.get(0).getScope();
600            }
601        }
602        return lcResult;
603    }
604
605    private Stack<TFrame> frameStack;
606
607    public void setFrameStack(Stack<TFrame> frameStack) {
608        this.frameStack = frameStack;
609    }
610
611    public Stack<TFrame> getFrameStack() {
612        return frameStack;
613    }
614
615    private TPTNodeList<TColumnWithSortOrder> indexColumns = null;
616
617    public TPTNodeList<TColumnWithSortOrder> getIndexColumns() {
618        return indexColumns;
619    }
620
621    private Stack<TObjectName> variableStack = null;
622
623    public void setVariableStack(Stack<TObjectName> variableStack) {
624        this.variableStack = variableStack;
625    }
626
627    public Stack<TObjectName> getVariableStack() {
628        if (variableStack == null){
629            variableStack = new Stack<TObjectName>();
630        }
631
632        return variableStack;
633    }
634
635    private Stack<TDaxFunction> daxFunctionStack = null;
636
637    public Stack<TDaxFunction> getDaxFunctionStack() {
638        if (daxFunctionStack == null){
639            daxFunctionStack = new Stack<TDaxFunction>();
640        }
641        return daxFunctionStack;
642    }
643
644    private TObjectName labelName;
645
646    public void setLabelName(TObjectName lName) {
647        labelName = lName;
648        if (labelName != null){
649            //labelName.setObjectType(TObjectName.ttobjLabelName);
650            labelName.setDbObjectType(EDbObjectType.label);
651        }
652    }
653
654    /**
655     *
656     * @return label name used in plsql statement.
657     */
658    public TObjectName getLabelName() {
659
660        return labelName;
661    }
662
663
664    private TObjectName endlabelName;
665
666    public void setEndlabelName(TObjectName endlabelName) {
667        this.endlabelName = endlabelName;
668    }
669
670    public TObjectName getEndlabelName() {
671
672        return endlabelName;
673    }
674
675    /**
676     * Type of this statement.
677     */
678    public ESqlStatementType sqlstatementtype;
679    /**
680     * Source tokens included in this statement. only source tokens available when this is a top level statement, otherwise, there is no source token in this statement.
681     * Please check {@link gudusoft.gsqlparser.nodes.TParseTreeNode#getStartToken()}, and {@link gudusoft.gsqlparser.nodes.TParseTreeNode#getEndToken()} of this statement. 
682     */
683    public TSourceTokenList sourcetokenlist;
684
685    public TSourceTokenList getTokenList() {
686        return sourcetokenlist;
687    }
688    /**
689     * Parser used to parse this statement.
690     */
691    public TCustomParser parser;
692    /**
693     * PLSQL parser used to parse this statement.
694     */
695    public TCustomParser plsqlparser;
696    /**
697     * Tag used by parser internally.
698     */
699    public int dummytag;
700
701    /**
702     * target table in the delete/insert/update/create table statement.
703     * @see #joins
704     * @see TSelectSqlStatement
705     * @see TDeleteSqlStatement
706     * @see TUpdateSqlStatement
707     * @see TCreateTableSqlStatement
708     * @see gudusoft.gsqlparser.stmt.TMergeSqlStatement
709     */
710    public TTable getTargetTable() {
711        return targetTable;
712    }
713
714    public void setTargetTable(TTable targetTable) {
715        setNewSubNode(this.targetTable,targetTable,getAnchorNode());
716        this.targetTable = targetTable;
717    }
718
719    private TTable targetTable ;
720
721    /**
722     * joins represents table sources in the from clause. All structure information was reserved.
723     * <p>SQL 1:
724     * <p><blockquote><pre>select f from t1</pre></blockquote>
725     * <p>size of joins will be 1, t1 can be fetch via joins.getJoin(0).getTable()
726     * <p>
727     * <p>SQL 2:
728     * <p><blockquote><pre>select f from t1,t2</pre></blockquote>
729     * <p>size of joins will be 2,
730     * <p>t1 can be fetch via joins.getJoin(0).getTable()
731     * <p>t2 can be fetch via joins.getJoin(1).getTable()
732     * <p>
733     * <p>SQL 3:
734     * <p><blockquote><pre>select f from t1 join t2 on t1.f1 = t2.f1</pre></blockquote>
735     * <p>size of joins will be 1,
736     * <p>t1 information can be fetch via joins.getJoin(0).getTable()
737     * <p>In order to access t2, we need to introduce a new  class {@link TJoinItem} which includes all information about t2 and join condition.
738     * <p>There is a property named joinItems of {@link TJoin} which is type of {@link TJoinItemList} that includes a list of {@link TJoinItem}.
739     * <p>this property can be access via {@link gudusoft.gsqlparser.nodes.TJoin#getJoinItems()}.
740     * <p>Now, t2 can be fetch via  joins.getJoin(0).getJoinItems().getJoinItem(0).getTable()
741     * <p>
742     * <p>SQL 4:
743     * <p><blockquote><pre>select f from t1 join t2 on t1.f1 = t2.f1 join t3 on t1.f1 = t3.f1</pre></blockquote>
744     * <p>size of joins will be 1,
745     * <p>t1 can be fetch via joins.getJoin(0).getTable()
746     * <p>t2 can be fetch via joins.getJoin(0).getJoinItems().getJoinItem(0).getTable()
747     * <p>t3 can be fetch via joins.getJoin(0).getJoinItems().getJoinItem(1).getTable()
748     *
749     * @see #tables
750     */
751    public TJoinList joins;
752
753    /**
754     * Provides a quick way to access all tables involved in this SQL statement.
755     * <p>It stores all tables in a flat way while {@link #joins} stores all tables in a hierarchical structure.
756     * <p>joins only represents tables in from clause of select/delete statement, and tables in update/insert statement.
757     * <p>{@link #tables} includes all tables in all types of SQL statements  such as tables involved in a create table or create trigger statements.
758     */
759    public TTableList tables;
760
761    public TJoinList getJoins() {
762        return joins;
763    }
764
765    public TTableList getTables() {
766        return tables;
767    }
768
769    /**
770     * Saves all first level sub statements.
771     * <p>By iterating statements recursively, you can fetch all included statements in an easy way.
772     * <p><blockquote><pre>
773     * select f1+(select f2 from t2) from t1
774     * where f2 &gt; all (select f3 from t3 where f4 = (select f5 from t4))</pre>
775     * </blockquote>
776     * <p> Statements included in above SQL was save in a hierarchical way like this:
777     * <ul>
778     * <li>(select f2 from t2)</li>
779     * <li>(select f3 from t3 where f4 = (select f5 from t4))
780     *     <ul>
781     *      <li>(select f5 from t4)</li>
782     *     </ul>
783     * </li>
784     * </ul>
785     * <p>If this statement is a create procedure/function statement, then all declaration statements and statements in
786     * procedure body can also be fetched quickly by iterating this property recursively.
787     *
788     *
789     */
790    public TStatementList getStatements() {
791        if (statements == null){
792            statements = new TStatementList();
793        }
794        return statements;
795    }
796
797    private TStatementList statements;
798
799    public void setCteList(TCTEList cteList) {
800        setNewSubNode(this.cteList,cteList,getAnchorNode());
801        this.cteList = cteList;
802    }
803
804    /**
805     * Multiple common table expressions {@link TCTE} can be specified following the single WITH keyword.
806     *<p> Each common table expression specified can also be referenced by name in the FROM clause of subsequent common table expressions.
807     *
808     * <p>Used in select, delete, update statement.
809     * @return  List of common table expression.
810     */
811
812    public TCTEList getCteList() {
813
814        return cteList;
815    }
816
817    private TCTEList cteList = null;
818
819    public void setResultColumnList(TResultColumnList resultColumnList) {
820        setNewSubNode(this.resultColumnList,resultColumnList,getAnchorNode());
821        this.resultColumnList = resultColumnList;
822    }
823
824    /**
825     * In select statement, this method returns Items in select_list.
826     * Can be *, expr, and name.*
827     * <br><br>
828     * In update statement, this method returns assignments in set clause.
829     *
830     * @return select list of select statement or assignments of update statement.
831     */
832    public TResultColumnList getResultColumnList() {
833
834        return resultColumnList;
835    }
836
837    private TResultColumnList resultColumnList = null;
838
839    private TWhereClause whereClause = null;
840    private TTopClause topClause = null;
841    private TOutputClause outputClause = null;
842    private TReturningClause returningClause = null;
843
844    public void setReturningClause(TReturningClause returningClause) {
845        setNewSubNode(this.returningClause,returningClause,getAnchorNode());
846        this.returningClause = returningClause;
847    }
848
849    /**
850     * @return {@link TReturningClause returning clause.}
851     */
852
853    public TReturningClause getReturningClause() {
854
855        return returningClause;
856    }
857
858    public void setOutputClause(TOutputClause outputClause) {
859        setNewSubNode(this.outputClause,outputClause,getAnchorNode());
860        this.outputClause = outputClause;
861    }
862
863    /**
864     * @return output clause.
865     */
866
867    public TOutputClause getOutputClause() {
868
869        return outputClause;
870    }
871
872    public void setTopClause(TTopClause topClause) {
873        setNewSubNode(this.topClause,topClause,getAnchorNode());
874        this.topClause = topClause;
875    }
876
877    /**
878     * @return {@link TTopClause top clause.}
879     */
880    public TTopClause getTopClause() {
881        return topClause;
882    }
883
884    public void setWhereClause(TWhereClause newWhereClause){
885        setNewSubNode(this.whereClause ,newWhereClause,getAnchorNode());
886        this.whereClause = newWhereClause;
887    }
888
889
890    /**
891     * @deprecated As of 2.0.9.0, use {@link #setWhereClause(TWhereClause)} instead
892     * Or, use {@link TWhereClause#setText(String)}
893     *
894     * @param condition
895     * @return
896     */
897    public  TWhereClause addWhereClause(String condition){
898           return this.whereClause;
899    }
900
901    /**
902     * restrict the rows selected to those that satisfy one or more conditions.
903     * used in select, delete, update statement.
904     * @return {@link TWhereClause where clause.}
905     */
906    public TWhereClause getWhereClause() {
907        return whereClause;
908    }
909
910    public void setAlreadyAddToParent(boolean alreadyAddToParent) {
911        this.alreadyAddToParent = alreadyAddToParent;
912    }
913
914    private boolean alreadyAddToParent = false;
915
916    private boolean ableToIncludeCTE(ESqlStatementType sst){
917       return ((sst == ESqlStatementType.sstselect)
918               ||(sst == ESqlStatementType.sstupdate)
919               ||(sst == ESqlStatementType.sstinsert)
920               ||(sst == ESqlStatementType.sstdelete)
921               );
922    }
923
924    private TCTEList cteListInAllLevels = new TCTEList();
925
926    protected  TCTEList searchCTEList(Boolean stopAtFirstFinding){
927        cteListInAllLevels.clear();
928        if (cteList != null ) {
929            cteListInAllLevels.addAll(cteList);
930            if (stopAtFirstFinding) return cteListInAllLevels;
931        }
932
933        TCustomSqlStatement lcParent = this.parentStmt;
934        while (lcParent != null){
935            if (!ableToIncludeCTE(lcParent.sqlstatementtype)) break;
936
937            if (lcParent.cteList != null) {
938                cteListInAllLevels.addAll(lcParent.cteList);
939                if (stopAtFirstFinding)  break;
940            }
941            lcParent = lcParent.parentStmt;
942        }
943        return cteListInAllLevels;
944    }
945
946
947//    protected  TCTEList searchCTEList(){
948//       TCTEList ret = null;
949//       if (cteList != null ) {return cteList;}
950//       TCustomSqlStatement lcParent = this.parentStmt;
951//       while (lcParent != null){
952//           if (!ableToIncludeCTE(lcParent.sqlstatementtype)) break;
953//           ret = lcParent.cteList;
954//           if (ret != null) break;
955//           lcParent = lcParent.parentStmt;
956//       }
957//        return ret;
958//    }
959
960    public TCustomSqlStatement getParentStmt() {
961        return parentStmt;
962    }
963
964    public TParseTreeNode getParentObjectName(){
965        TParseTreeNode result = super.getParentObjectName();
966        if (result != null) return result;
967        return getParentStmt();
968    }
969
970    public void setParentStmt(TCustomSqlStatement parentStmt) {
971        if (!alreadyAddToParent){
972            this.parentStmt = parentStmt;
973            if (this.parentStmt != null){
974                parentStmt.getStatements().add(this);
975                alreadyAddToParent = true;
976            }
977        }
978    }
979
980    public void setParentStmtToNull() {
981        this.parentStmt = null;
982    }
983
984    private TCustomSqlStatement ancestorStmt = null;
985
986    public TCustomSqlStatement getAncestorStmt() {
987        TCustomSqlStatement lcRet = this;
988        while (lcRet.getParentStmt() != null){
989            lcRet = lcRet.getParentStmt();
990        }
991        return lcRet;
992    }
993
994    /**
995     * parent statement of this statement if any
996     */
997    private TCustomSqlStatement parentStmt = null;
998
999    /**
1000     * Original Parse tree node from parser
1001     */
1002    public TParseTreeNode rootNode;
1003
1004    private Stack symbolTable = null;
1005
1006    /**
1007     *
1008     * @deprecated since ver 2.5.3.5, please use {@link TStmtScope} instead
1009     */
1010    public Stack getSymbolTable() {
1011        if (symbolTable == null){
1012            symbolTable = new Stack();
1013        }
1014        return symbolTable;
1015    }
1016
1017    public TSourceToken semicolonended;
1018    public boolean isctequery;
1019    private ArrayList <TSyntaxError> syntaxErrors;
1020
1021    public ArrayList<TSyntaxError> getSyntaxErrors() {
1022        return syntaxErrors;
1023    }
1024
1025    public String getErrormessage(){
1026
1027        String s="",hint="Syntax error";
1028        TSyntaxError t;
1029        for (int i= 0; i< syntaxErrors.size(); i++)
1030        {
1031            t = (TSyntaxError) syntaxErrors.get(i);
1032            if (t.hint.length() > 0) hint = t.hint;
1033            s= s+hint+"("+t.errorno+") near: "+t.tokentext;
1034            s=s+"("+t.lineNo;
1035            s=s+","+t.columnNo +")";
1036            //s=s+" expected tokentext:"+t.hint;
1037
1038            // break;//get only one message, remove this one and uncomment next line to get all error messages
1039            if (i !=  syntaxErrors.size() - 1)
1040                s = s +TBaseType.linebreak;
1041        }
1042
1043        return s;
1044    }
1045
1046    public ArrayList<TSyntaxError> getSyntaxHints() {
1047        return syntaxHints;
1048    }
1049
1050    private ArrayList <TSyntaxError> syntaxHints;
1051
1052    protected boolean isparsed;
1053    TSourceToken _semicolon;
1054
1055    /**
1056     * Number of syntax errors for this statement.
1057     * @return 0 means no syntax error.
1058     */
1059    public int getErrorCount() {
1060        return syntaxErrors.size();
1061    }
1062
1063    
1064    public TCustomSqlStatement(EDbVendor dbvendor){
1065        super();
1066        this.dbvendor = dbvendor;
1067        sqlstatementtype = ESqlStatementType.sstunknown;
1068        dummytag = 0;
1069        sourcetokenlist = new TSourceTokenList();
1070        syntaxErrors = new ArrayList<TSyntaxError>(4);
1071        syntaxHints = new ArrayList<TSyntaxError>(4);
1072        tables = new TTableList();
1073        joins = new TJoinList();
1074        indexColumns = new TPTNodeList<TColumnWithSortOrder>();
1075     }
1076
1077    /**
1078     * Log error messages if syntax errors found while parsing this statement.
1079     * @param se syntax error structure.
1080     * @return  type of error
1081     */
1082    public EActionOnParseError parseerrormessagehandle(TSyntaxError se){
1083        if (se.errortype == EErrorType.sphint){
1084            this.getAncestorStmt().syntaxHints.add(se);
1085        }else
1086            this.getAncestorStmt().syntaxErrors.add(se);
1087        return EActionOnParseError.aopcontinue;
1088    }
1089
1090    public int parsestatement(TCustomSqlStatement pparentsql,boolean isparsetreeavailable){
1091        return parsestatement(pparentsql,isparsetreeavailable,false);
1092    }
1093
1094    /**
1095     * Parse this statement.
1096     * @param pparentsql
1097     * @param isparsetreeavailable
1098     * @return parse result, zero means no syntax error found.
1099     */
1100    public int parsestatement(TCustomSqlStatement pparentsql,boolean isparsetreeavailable, boolean onlyNeedRawParseTree){
1101        int ret = 0;
1102        isparsed = false;
1103        if (!isparsetreeavailable){
1104           ret = checksyntax(pparentsql);
1105        }
1106        if (ret == 0)
1107        {
1108            isparsed = true;
1109            if (!onlyNeedRawParseTree){
1110                ret = doParseStatement(pparentsql);
1111            }
1112        }else if ((dbvendor == EDbVendor.dbvsybase)||(dbvendor == EDbVendor.dbvsybasease || dbvendor == EDbVendor.dbvsqlanywhere || dbvendor == EDbVendor.dbvsybaseiq)){
1113            if ((this.rootNode != null)&&
1114                    ((sqlstatementtype == ESqlStatementType.sstmssqlcreateprocedure)
1115                     ||(sqlstatementtype == ESqlStatementType.sstmssqlcreatefunction)
1116                     ||(sqlstatementtype == ESqlStatementType.sstcreatetrigger)
1117                    )){
1118                if (!onlyNeedRawParseTree){
1119                    doParseStatement(pparentsql);
1120                }
1121
1122            }
1123        }
1124        return ret;
1125    }
1126
1127    public boolean OracleStatementCanBeSeparatedByBeginEndPair(){
1128        return  (
1129
1130                (this.sqlstatementtype == ESqlStatementType.sstplsql_createprocedure)
1131                //|| (this.sqlstatementtype == ESqlStatementType.sst_block_with_label)
1132                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createfunction)
1133                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createpackage)
1134                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtype_placeholder)
1135                ||(this.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody)
1136                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtrigger)
1137//                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtypebody)
1138//                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_tabletypedef)
1139                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_varraytypedef)
1140                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createprocedure)
1141                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_execimmestmt)
1142                ||(this.sqlstatementtype == ESqlStatementType.sstoraclecreatelibrary)
1143                );
1144    }
1145
1146    public boolean VerticaStatementCanBeSeparatedByBeginEndPair(){
1147        return  (
1148
1149                        (this.sqlstatementtype == ESqlStatementType.sstcreatefunction)
1150        );
1151    }
1152
1153    public boolean isnzplsql(){
1154        return  (
1155                (this.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1156        );
1157    }
1158
1159    public boolean ispgplsql(){
1160        return (this instanceof TCommonBlock)
1161                ||(this.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1162                ||(this.sqlstatementtype == ESqlStatementType.sstcreatefunction)
1163                ||(this.sqlstatementtype == ESqlStatementType.sstDoExecuteBlock)
1164                ;
1165    }
1166
1167    public boolean isGaussDBStoredProcedure(){
1168        return (this instanceof TCommonBlock)
1169                ||(this.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1170                ||(this.sqlstatementtype == ESqlStatementType.sstcreatefunction)
1171                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createpackage)
1172                ||(this.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody)
1173                ||(this.sqlstatementtype == ESqlStatementType.sstDoExecuteBlock)
1174                ||(this.sqlstatementtype == ESqlStatementType.sstcreatetrigger)
1175                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtypebody)
1176                ;
1177    }
1178
1179    public boolean isdatabricksplsql(){
1180        return (this instanceof TCommonBlock)
1181                ;
1182    }
1183
1184    public boolean isgreeplumplsql(){
1185        return (this instanceof TCommonBlock)
1186                ||(this.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1187                ||(this.sqlstatementtype == ESqlStatementType.sstcreatefunction)
1188                ||(this.sqlstatementtype == ESqlStatementType.sstDoExecuteBlock)
1189                ;
1190    }
1191
1192    public boolean isathenaplsql(){
1193        return (this instanceof TCommonBlock)
1194                ;
1195    }
1196
1197    public boolean isprestoplsql(){
1198        return (this instanceof TCommonBlock)
1199                ;
1200    }
1201
1202    public boolean issnowflakeplsql(){
1203        return ((this instanceof TCommonBlock)
1204                ||(this.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1205        );
1206    }
1207
1208    public boolean isBigQueryplsql(){
1209        return ((this instanceof TCommonBlock)
1210                ||(this.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
1211        );
1212    }
1213
1214    public boolean isverticaplsql(){
1215        return  (
1216                        (this.sqlstatementtype == ESqlStatementType.sstcreatefunction)
1217        );
1218    }
1219
1220    public boolean isoracleplsql(){
1221        return  (
1222                (this.sqlstatementtype == ESqlStatementType.sst_plsql_block)
1223                ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createprocedure)
1224                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createfunction)
1225                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createpackage)
1226                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtype_placeholder)
1227                        ||(this.sqlstatementtype == ESqlStatementType.sstoraclecreatepackagebody)
1228                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtrigger)
1229                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createtypebody)
1230                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_tabletypedef)
1231                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_varraytypedef)
1232                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_createprocedure)
1233                        ||(this.sqlstatementtype == ESqlStatementType.sstplsql_execimmestmt)
1234                        ||(this.sqlstatementtype == ESqlStatementType.sstoraclecreatelibrary)
1235                );
1236    }
1237
1238    int checksyntax(TCustomSqlStatement psql){
1239        return dochecksyntax(psql);
1240    }
1241
1242    protected int dochecksyntax(TCustomSqlStatement psql){
1243        int ret = -1;
1244        clear();
1245        if (sourcetokenlist.size() == 0) return ret;
1246
1247//        TCustomParser lcparser;
1248//        lcparser = new TLzParserOracleSql(sourcetokenlist);
1249//        lcparser.sql = this;
1250//        ret = lcparser.yyparse();
1251
1252
1253        if (((this.dbvendor == EDbVendor.dbvoracle)||(this.dbvendor == EDbVendor.dbvoceanbase))&&(this.isoracleplsql()&&(plsqlparser!=null))
1254//                || ((this.dbvendor == EDbVendor.dbvgaussdb) // gaussdb 中用 oracle plsql 写的存储过程,用 oracle plsql parser 来解析
1255//                        &&(
1256//                            ((this instanceof TCreateFunctionStmt)&&(((TCreateFunctionStmt)this).isGaussDBSpInOracle()))
1257//                            ||((this instanceof TCreateProcedureStmt)&&(((TCreateProcedureStmt)this).isGaussDBSpInOracle()))
1258//                            ||(this instanceof TPlsqlCreatePackage)
1259//                          )
1260//                )
1261        ){
1262            plsqlparser.sql = this;
1263//            if (this.dbvendor == EDbVendor.dbvgaussdb){
1264//                // 原来用 gaussDB lexer tokenize 的 token 需要用 Oracle lexer 重新 tokenize 一边
1265//                String sqlText="";
1266//                for(int k = 0;k<sourcetokenlist.size();k++){
1267//                    sqlText = sqlText + sourcetokenlist.get(k).toString();
1268//                }
1269//                // TODO, need to use singleton pattern to get a single instance of Oracle parser.
1270//                TGSqlParser sqlParser = new TGSqlParser(EDbVendor.dbvoracle);
1271//                // keep coordinate of the origin query
1272//                long originalLineNo = sourcetokenlist.get(0).lineNo;
1273//                long originalColumnNo = sourcetokenlist.get(0).columnNo;
1274//                sqlParser.sqltext = TBaseType.stringBlock((int) originalLineNo - 1,(int) originalColumnNo - 1)+ sqlText;;
1275//
1276//                int r = sqlParser.getrawsqlstatements();
1277//                sourcetokenlist.clear();
1278//                for(int k=0;k<sqlParser.sourcetokenlist.size();k++){
1279//                    sourcetokenlist.add(sqlParser.sourcetokenlist.get(k));
1280//                }
1281//            }
1282
1283            plsqlparser.sourcetokenlist = sourcetokenlist;
1284
1285            if ((this instanceof TCommonStoredProcedureSqlStatement)
1286                &&((TCommonStoredProcedureSqlStatement)this).isWrapped()){
1287                // don't parse wrapped oracle plsql
1288                ret = 0;
1289                this.rootNode = this;
1290            }else {
1291                ret = plsqlparser.yyparse();
1292                this.rootNode = plsqlparser.rootNode;
1293            }
1294        }
1295        else{
1296            if ((this.sqlstatementtype == ESqlStatementType.sstExplain)&&(dbvendor != EDbVendor.dbvhana)){
1297                    // EXPLAIN PLAN ... FOR statement; only parse token after FOR keyword
1298                    boolean isFoundStopToken = false;
1299
1300                    for(int k=0;k<sourcetokenlist.size();k++){
1301                        TSourceToken st = sourcetokenlist.get(k);
1302                        switch (dbvendor){
1303                            case dbvoracle:
1304                                if (st.tokencode == TBaseType.rrw_for) {
1305                                    st.tokencode = TBaseType.sqlpluscmd;
1306                                    isFoundStopToken = true;
1307                                }
1308                                break;
1309                            case dbvdameng:
1310                                if (st.tokencode == TBaseType.rrw_for) {
1311                                    st.tokencode = TBaseType.sqlpluscmd;
1312                                    isFoundStopToken = true;
1313                                }
1314                                break;
1315                            case dbvredshift:
1316                                if (st.tokencode == TBaseType.rrw_explain){
1317                                    st.tokencode = TBaseType.sqlpluscmd;
1318                                    TSourceToken nextst = st.nextSolidToken();
1319                                    if (nextst.tokencode == TBaseType.rrw_redshift_verbose){
1320                                        nextst.tokencode = TBaseType.sqlpluscmd;
1321                                        //System.out.println("Found verbose after explain");
1322                                    }
1323                                    isFoundStopToken = true;
1324                                }
1325                                break;
1326                            case dbvvertica:
1327                                if ((st.tokencode == TBaseType.rrw_select)
1328                                        ||(st.tokencode == TBaseType.rrw_insert)
1329                                        ||(st.tokencode == TBaseType.rrw_update)
1330                                        ||(st.tokencode == TBaseType.rrw_merge)
1331                                )
1332                                {
1333                                    isFoundStopToken = true;
1334                                }
1335                                break;
1336                            case dbvclickhouse:
1337                            case dbvmysql:
1338                            case dbvoceanbase:
1339                            case dbvsparksql:
1340                            case dbvdatabricks:
1341                                if ((st.tokencode == TBaseType.rrw_select)
1342                                        ||(st.tokencode == TBaseType.rrw_insert)
1343                                        ||(st.tokencode == TBaseType.rrw_update)
1344                                        ||(st.tokencode == TBaseType.rrw_delete)
1345                                        ||(st.tokencode == TBaseType.rrw_replace)
1346                                        ||(st.tokencode == TBaseType.rrw_with)
1347                                        ||(st.tokencode == TBaseType.rrw_create)
1348                                        ||(st.tokencode == '(')
1349                                )
1350                                {
1351                                    isFoundStopToken = true;
1352                                }
1353                                break;
1354                            case dbvpostgresql:
1355                                if ((st.tokencode == TBaseType.rrw_select)
1356                                        ||(st.tokencode == TBaseType.rrw_insert)
1357                                        ||(st.tokencode == TBaseType.rrw_update)
1358                                        ||(st.tokencode == TBaseType.rrw_delete)
1359                                        ||(st.tokencode == TBaseType.rrw_replace)
1360                                        ||(st.tokencode == TBaseType.rrw_with)
1361                                        ||(st.tokencode == TBaseType.rrw_create)
1362                                        ||(st.tokencode == TBaseType.rrw_execute)
1363                                )
1364                                {
1365                                    isFoundStopToken = true;
1366                                }else if (st.tokencode == '('){
1367                                    // Check if this '(' starts EXPLAIN options like (COSTS FALSE)
1368                                    // or a subquery like (SELECT ...)
1369                                    TSourceToken nextSolid = sourcetokenlist.nextsolidtoken(k, 1, false);
1370                                    if (nextSolid != null
1371                                            && nextSolid.tokencode != TBaseType.rrw_select
1372                                            && nextSolid.tokencode != TBaseType.rrw_insert
1373                                            && nextSolid.tokencode != TBaseType.rrw_update
1374                                            && nextSolid.tokencode != TBaseType.rrw_delete
1375                                            && nextSolid.tokencode != TBaseType.rrw_with){
1376                                        // Options list: skip past closing ')'
1377                                        int depth = 1;
1378                                        st.tokencode = TBaseType.sqlpluscmd;
1379                                        for (k = k + 1; k < sourcetokenlist.size() && depth > 0; k++){
1380                                            TSourceToken inner = sourcetokenlist.get(k);
1381                                            if (inner.tokencode == '(') depth++;
1382                                            else if (inner.tokencode == ')') depth--;
1383                                            inner.tokencode = TBaseType.sqlpluscmd;
1384                                        }
1385                                        k--; // adjust for loop increment
1386                                    }else{
1387                                        isFoundStopToken = true;
1388                                    }
1389                                }
1390                                break;
1391                            case dbvduckdb:
1392                                if ((st.tokencode == TBaseType.rrw_select)
1393                                        ||(st.tokencode == TBaseType.rrw_insert)
1394                                        ||(st.tokencode == TBaseType.rrw_update)
1395                                        ||(st.tokencode == TBaseType.rrw_delete)
1396                                        ||(st.tokencode == TBaseType.rrw_replace)
1397                                        ||(st.tokencode == TBaseType.rrw_with)
1398                                        ||(st.tokencode == TBaseType.rrw_create)
1399                                        ||(st.tokencode == TBaseType.rrw_alter)
1400                                        ||(st.tokencode == TBaseType.rrw_merge)
1401                                        ||(st.tokencode == '(')
1402                                )
1403                                {
1404                                    isFoundStopToken = true;
1405                                }
1406                                break;
1407                            case dbvflink:
1408                                // Flink EXPLAIN can have: EXPLAIN (options) stmt or EXPLAIN options stmt
1409                                // Don't stop at '(' because it might be part of EXPLAIN (ESTIMATED_COST, ...)
1410                                if ((st.tokencode == TBaseType.rrw_select)
1411                                        ||(st.tokencode == TBaseType.rrw_insert)
1412                                        ||(st.tokencode == TBaseType.rrw_update)
1413                                        ||(st.tokencode == TBaseType.rrw_delete)
1414                                        ||(st.tokencode == TBaseType.rrw_replace)
1415                                        ||(st.tokencode == TBaseType.rrw_with)
1416                                        ||(st.tokencode == TBaseType.rrw_create)
1417                                )
1418                                {
1419                                    isFoundStopToken = true;
1420                                }
1421                                break;
1422                            case dbvcouchbase:
1423                                if (st.tokencode == TBaseType.rrw_explain){
1424                                    st.tokencode = TBaseType.sqlpluscmd;
1425                                    isFoundStopToken = true;
1426                                }
1427                                break;
1428                            case dbvpresto:
1429                            case dbvathena:
1430                            case dbvnetezza:
1431                                if ((st.tokencode == TBaseType.rrw_select)
1432                                        ||(st.tokencode == TBaseType.rrw_insert)
1433                                        ||(st.tokencode == TBaseType.rrw_update)
1434                                        ||(st.tokencode == TBaseType.rrw_delete)
1435                                )
1436                                {
1437                                    isFoundStopToken = true;
1438                                }
1439                                break;
1440                            case dbvteradata:
1441                                if ((st.tokencode == TBaseType.rrw_select)
1442                                        ||(st.tokencode == TBaseType.rrw_insert)
1443                                        ||(st.tokencode == TBaseType.rrw_update)
1444                                        ||(st.tokencode == TBaseType.rrw_delete)
1445                                        ||(st.tokencode == TBaseType.rrw_teradata_collect)
1446                                )
1447                                {
1448                                    isFoundStopToken = true;
1449                                }
1450                                break;
1451                        }//switch
1452
1453                        if (isFoundStopToken) break;
1454                        st.tokencode = TBaseType.sqlpluscmd;
1455                    }
1456            }else if (this.sqlstatementtype == ESqlStatementType.sstProfile){
1457                for(int k=0;k<sourcetokenlist.size();k++) {
1458                    TSourceToken st = sourcetokenlist.get(k);
1459                    if (dbvendor == EDbVendor.dbvvertica){
1460                        if ((st.tokencode == TBaseType.rrw_select)
1461                                ||(st.tokencode == TBaseType.rrw_insert)
1462                                ||(st.tokencode == TBaseType.rrw_update)
1463                                ||(st.tokencode == TBaseType.rrw_merge)
1464                                )
1465                        {
1466                            break;
1467                        }
1468                    }
1469                    st.tokencode = TBaseType.sqlpluscmd;
1470                }
1471            }else if (this.sqlstatementtype == ESqlStatementType.sstprepare){
1472                if ((dbvendor == EDbVendor.dbvcouchbase)||(dbvendor == EDbVendor.dbvpresto)||(dbvendor == EDbVendor.dbvathena)){
1473                    int keywordCount = 0;
1474                    for(int k=0;k<sourcetokenlist.size();k++) {
1475                        TSourceToken st = sourcetokenlist.get(k);
1476                            if (st.tokencode == TBaseType.rrw_prepare)
1477                            {
1478                                keywordCount++;
1479                            }else if ((st.tokentype == ETokenType.ttkeyword)
1480                                    &&(st.tokencode != TBaseType.rrw_from)
1481                                    &&(st.tokencode != TBaseType.rrw_as)){
1482                                keywordCount++;
1483                                if (keywordCount > 1) break;
1484                            }
1485                        st.tokencode = TBaseType.sqlpluscmd;
1486                    }
1487                }
1488            }
1489
1490            if (parser == null){
1491                // statement such as select/insert and etc  inside plsql
1492                if (psql != null){
1493                    parser = psql.getTopStatement().parser;
1494                    this.setParentStmt(psql);
1495                }
1496                // parser =  new TParserOracleSql(null);
1497                //parser.lexer = new TLexerOracle();
1498                //parser.lexer.delimiterchar = '/';
1499            }
1500            parser.sql = this;
1501            parser.sourcetokenlist = sourcetokenlist;
1502            ret = parser.yyparse();
1503            this.rootNode = parser.rootNode;
1504        }
1505
1506        if (ret == 0){
1507            ret = syntaxErrors.size();
1508        }
1509       // if (rootNode == null) {
1510       if (rootNode == null) {
1511            // EXPLAIN FOR CONNECTION has no inner statement to parse - this is valid
1512            if (this.sqlstatementtype == ESqlStatementType.sstExplain && isExplainForConnection()) {
1513                ret = 0;
1514            } else {
1515                ret = TBaseType.MSG_ERROR_NO_ROOT_NODE;
1516                // todo , uncomment next line when all sql statements in .y file was processed
1517                 parseerrormessagehandle( new TSyntaxError("no root node",0,0,"no_root_node",EErrorType.sperror,TBaseType.MSG_ERROR_NO_ROOT_NODE,this,-1));
1518            }
1519        }
1520        return ret;
1521    }
1522
1523    private boolean isExplainForConnection() {
1524        if (sourcetokenlist == null) return false;
1525        for (int i = 0; i < sourcetokenlist.size() - 1; i++) {
1526            TSourceToken st = sourcetokenlist.get(i);
1527            if (st.toString().equalsIgnoreCase("for")) {
1528                TSourceToken next = sourcetokenlist.nextsolidtoken(i, 1, false);
1529                if (next != null && next.toString().equalsIgnoreCase("connection")) {
1530                    return true;
1531                }
1532            }
1533        }
1534        return false;
1535    }
1536
1537    public void clearError(){
1538        syntaxErrors.clear();
1539        syntaxHints.clear();
1540    }
1541
1542    void clear(){
1543        syntaxErrors.clear();
1544        syntaxHints.clear();
1545// todo all subclass should add super()       
1546    }
1547
1548    public void setStmtScope(TStmtScope stmtScope) {
1549        this.stmtScope = stmtScope;
1550    }
1551
1552    public TStmtScope getStmtScope() {
1553        return stmtScope;
1554    }
1555
1556    /**
1557     * Original SQL fragment of this statement.
1558     * @return   Original statement text.
1559     */
1560
1561    /*
1562    public String toString(){
1563       StringBuffer sb = new StringBuffer("");
1564       for(int i=0; i<sourcetokenlist.size();i++){
1565          sb.append(sourcetokenlist.get(i).toString());  
1566        }
1567       return sb.toString();
1568    }
1569    */
1570    protected TStmtScope stmtScope = null;
1571    void buildsql(){}
1572    public int doParseStatement(TCustomSqlStatement psql){
1573        if (psql != null){
1574            this.setParentStmt(psql);
1575            this.setFrameStack(psql.getFrameStack());
1576            psql.stmtScope.incrementCurrentStmtIndex();
1577            this.queryId = String.format("%s#stmt_%d_%s", psql.getQueryId(),psql.stmtScope.getCurrentStmtIndex(), this.sqlstatementtype);
1578            stmtScope = new TStmtScope(psql.stmtScope,this);
1579            // psql.statements.add(this);
1580        }else{
1581            stmtScope = new TStmtScope(this);
1582
1583            // global scope
1584            this.getFrameStack().peek().getScope().incrementCurrentStmtIndex();
1585            this.queryId = String.format("stmt_%d_%s",this.getFrameStack().peek().getScope().getCurrentStmtIndex(), this.sqlstatementtype);
1586        }
1587
1588        if ((this.getStartToken() == null)&&(rootNode != null)){
1589            this.setStartToken(rootNode.getStartToken());
1590        }
1591        if ((this.getEndToken() == null)&&(rootNode != null)){
1592            this.setEndToken(rootNode.getEndToken());
1593        }
1594
1595        if(this.getGsqlparser() == null){
1596            if (rootNode != null){
1597                this.setGsqlparser(rootNode.getGsqlparser());
1598            }
1599        }
1600        return 0;
1601    }
1602
1603    void addtokentolist(TSourceToken st){
1604       st.stmt = this;
1605       sourcetokenlist.add(st);
1606    }
1607
1608    public TTable analyzeTablename(TObjectName tableName){
1609        TTable lcTable = new TTable();
1610        lcTable.setTableType(ETableSource.objectname);
1611        lcTable.setStartToken(tableName.getStartToken());
1612        lcTable.setEndToken(tableName.getEndToken());
1613        lcTable.setGsqlparser(this.getGsqlparser());
1614        lcTable.setTableName(tableName);
1615
1616        tables.addTable(lcTable);
1617        return lcTable;
1618    }
1619
1620    /**
1621     * PostgreSQL family: the target of an INSERT, UPDATE, DELETE or MERGE is always a table, never a CTE of a WITH
1622     * (measured on PostgreSQL 17: {@code WITH t AS (DELETE ...) UPDATE t ...} updates table t, and
1623     * {@code WITH d1 AS (SELECT ...), d2 AS (INSERT INTO d1 SELECT a FROM d1 ...)} inserts into table d1 while its
1624     * FROM d1 reads the CTE). A target the CTE lookup bound to a CTE of the same name is unbound.
1625     */
1626    protected void unbindCteTarget(TTable target) {
1627        if (target != null && target.isCTEName() && (dbvendor == EDbVendor.dbvpostgresql
1628                || dbvendor == EDbVendor.dbvgreenplum || dbvendor == EDbVendor.dbvedb)) {
1629            target.setCTEName(false);
1630            target.setCTE(null);
1631            target.setCteColomnReferences(null);
1632        }
1633    }
1634
1635    protected boolean isTableACTE(TTable pTable){
1636        boolean lcResult = false;
1637        TCTEList cteList1 = getCteList();
1638        if (cteList1 == null){
1639            TCustomSqlStatement lcStmt = getParentStmt();
1640            while (lcStmt != null){
1641                if (lcStmt.getCteList() != null){
1642                    cteList1 = lcStmt.getCteList();
1643                    break;
1644                }else {
1645                    lcStmt = lcStmt.getParentStmt();
1646                }
1647            }
1648        }
1649        if (cteList1 == null) return  false;
1650       // TCTE lcCTE = cteList1.cteNames.get(TBaseType.getTextWithoutQuoted(pTable.toString()).toUpperCase());
1651        if (pTable.toString() == null) return false;
1652
1653        int searchPos = pTable.getStartToken().posinlist;
1654        if (this.getCteIncludeThisStmt() != null){
1655            searchPos = this.getCteIncludeThisStmt().getStartToken().posinlist;
1656        }
1657        TCTE lcCTE = cteList1.searchCTEByName(TBaseType.getTextWithoutQuoted(pTable.toString()).toUpperCase(java.util.Locale.ROOT),searchPos);
1658        if ( lcCTE != null ){
1659            if (pTable.setCTE(lcCTE)){
1660                pTable.setCTEName(true);
1661                lcResult = true;
1662            }
1663        }
1664//        for (int i=0;i<cteList1.size();i++){
1665//            lcCTE = cteList1.getCTE(i);
1666//            if (TBaseType.getTextWithoutQuoted(lcCTE.getTableName().toString()).equalsIgnoreCase(TBaseType.getTextWithoutQuoted(pTable.toString()))){
1667//                pTable.setCTEName(true);
1668//                pTable.setCTE(lcCTE);
1669//                lcResult = true;
1670//                break;
1671//            }
1672//        }
1673
1674        return lcResult;
1675
1676    }
1677
1678    public TTable findTable(ETableEffectType[] tableEffectTypes){
1679        TTable lcResult = null;
1680        for(int i=0;i<tables.size();i++){
1681            for(int j=0;j<tableEffectTypes.length;j++){
1682                if (tables.getTable(i).getEffectType() == tableEffectTypes[j]){
1683                    lcResult = tables.getTable(i);
1684                    return  lcResult;
1685                }
1686            }
1687        }
1688        return  lcResult;
1689    }
1690    public void addToTables(TTable pTable){
1691        tables.addTable(pTable);
1692        if (isTableACTE(pTable)) return;
1693
1694        if (pTable.getTableName() == null) return;
1695        if (pTable.getTableName().getTableToken() == null) return;
1696        if ((pTable.getTableName().getTableString().toString().equalsIgnoreCase("inserted"))||(pTable.getTableName().getTableString().toString().equalsIgnoreCase("deleted"))){
1697           if ((getAncestorStmt().sqlstatementtype == ESqlStatementType.sstcreatetrigger)
1698               ||(getAncestorStmt().sqlstatementtype == ESqlStatementType.sstmssqlaltertrigger)){
1699               //pTable.setLinkTable(true);
1700               ETableEffectType[] effectTypes = new ETableEffectType[]{
1701                       ETableEffectType.tetTriggerOn,ETableEffectType.tetTriggerInsert,ETableEffectType.tetTriggerDelete,ETableEffectType.tetTriggerUpdate,ETableEffectType.tetTriggerInsteadOf
1702               };
1703               pTable.setLinkTable(getAncestorStmt().findTable(effectTypes));
1704           }
1705        }
1706
1707    }
1708
1709    public TJoin analyzeTableOrJoin(TFromTable pfromTable){
1710        TFromTable lcFromTable = pfromTable;
1711        TJoin lcJoin;
1712        TTable lcTable;
1713
1714        if (lcFromTable.getFromtableType() != ETableSource.join){
1715            lcJoin = new TJoin();
1716            lcTable = analyzeFromTable(lcFromTable,true);
1717            lcTable.setEffectType(ETableEffectType.tetSelect);
1718            lcJoin.setTable(lcTable);
1719            lcJoin.setStartToken(lcJoin.getTable().getStartToken());
1720            lcJoin.setEndToken(lcJoin.getTable().getEndToken());
1721            lcJoin.setGsqlparser(getGsqlparser());
1722            this.fromSourceTable = lcTable;
1723            this.getRelations().add(lcTable);
1724        }else{
1725            this.fromSourceJoin = lcFromTable.getJoinExpr();
1726
1727            this.fromSourceTable = new TTable();
1728            this.fromSourceTable.setTableType(ETableSource.join);
1729            this.fromSourceTable.setAliasClause(lcFromTable.getJoinExpr().getAliasClause());
1730            this.fromSourceTable.setStartToken(lcFromTable.getStartToken());
1731            this.fromSourceTable.setEndToken(lcFromTable.getEndToken());
1732            this.fromSourceTable.setGsqlparser(lcFromTable.getGsqlparser());
1733            this.fromSourceTable.setJoinExpr(this.fromSourceJoin);
1734            this.getRelations().add(this.fromSourceTable);
1735
1736            lcJoin = analyzeJoin(lcFromTable.getJoinExpr(),null,true);
1737            lcJoin.doParse(this, ESqlClause.join);
1738
1739            if (lcFromTable.getLateralViewList() != null){
1740                for(TLateralView lateralView:lcFromTable.getLateralViewList()){
1741                    TTable t = lateralView.createATable(this);
1742                    addToTables(t);
1743                    this.relations.add(t);
1744                }
1745            }
1746        }
1747
1748        return lcJoin;
1749    }
1750
1751    public TTable analyzeFromTable(TFromTable pfromTable, Boolean addToTableList){
1752        return analyzeFromTable(pfromTable,addToTableList,ESqlClause.unknown);
1753    }
1754
1755    public TTable analyzeFromTable(TFromTable pfromTable, Boolean addToTableList, ESqlClause pLocation){
1756        TTable lcTable = new TTable();
1757        lcTable.setTableType(pfromTable.getFromtableType());
1758        lcTable.setAliasClause(pfromTable.getAliasClause());
1759        lcTable.setStartToken(pfromTable.getStartToken());
1760        lcTable.setEndToken(pfromTable.getEndToken());
1761        lcTable.setGsqlparser(pfromTable.getGsqlparser());
1762        lcTable.setTableHintList(pfromTable.getTableHintList());
1763        lcTable.setTableSample(pfromTable.getTableSample());
1764        lcTable.setLateralViewList(pfromTable.getLateralViewList());
1765        lcTable.setTableProperties(pfromTable.getTableProperties());
1766        lcTable.setPivotedTable(pfromTable.getPivotedTable());
1767        lcTable.setParenthesisCount(pfromTable.getParenthesisCount());
1768        lcTable.setParenthesisAfterAliasCount(pfromTable.getParenthesisAfterAliasCount());
1769        lcTable.setTableKeyword(pfromTable.isTableKeyword());
1770        lcTable.setOnlyKeyword(pfromTable.isOnlyKeyword());
1771        lcTable.setArrayIndexAlias(pfromTable.getArrayIndexAlias());
1772        lcTable.setSuperUnpivot(pfromTable.isSuperUnpivot());
1773        lcTable.setTableOperatorInputAlias(pfromTable.getTableOperatorInputAlias());
1774        lcTable.setTableOperatorPartitionBy(pfromTable.getTableOperatorPartitionBy());
1775        lcTable.setTableOperatorOrderBy(pfromTable.getTableOperatorOrderBy());
1776        lcTable.setFlashback(pfromTable.getFlashback());
1777        lcTable.setPxGranule(pfromTable.getPxGranule());
1778        lcTable.setTimeTravelClause(pfromTable.getTimeTravelClause());
1779        //lcTable.setPartitionClause(pfromTable.getPartitionClause());
1780
1781        if(getFrameStack().firstElement() != null){
1782            TFrame stackFrame = getFrameStack().firstElement();
1783            TGlobalScope globalScope = (TGlobalScope)stackFrame.getScope();
1784            lcTable.setSqlEnv(globalScope.getSqlEnv());
1785        }
1786
1787       switch(lcTable.getTableType()){
1788           case objectname:{
1789              // tables.addTableByTableRefernce(pfromTable.getTableObjectName());
1790               boolean insertedInTrigger = false;
1791               if (getTopStatement().sqlstatementtype == ESqlStatementType.sstcreatetrigger){
1792                  insertedInTrigger = (pfromTable.getTableObjectName().toString().compareToIgnoreCase("inserted")==0);
1793               }
1794
1795               if (insertedInTrigger){
1796                   // change table name from inserted to onTable name in create trigger 
1797                 lcTable.setTableName(((TCreateTriggerStmt)getTopStatement()).getOnTable().getTableName());
1798                 //lcTable.setLinkTable(true);
1799                 lcTable.setLinkTable(((TCreateTriggerStmt)getTopStatement()).getOnTable());
1800
1801               }else{
1802                    lcTable.setTableName(pfromTable.getTableObjectName());
1803                   lcTable.getTableName().setSqlEnv(getSqlEnv());
1804
1805//                   if (getSqlEnv().getDefaultCatalogName() != null){
1806//                       if (lcTable.getTableName().getDatabaseToken() == null){
1807//                           lcTable.getTableName().setDatabaseToken(new TSourceToken(getSqlEnv().getDefaultCatalogName()),true);
1808//                       }
1809//                   }
1810
1811//                   if ((lcTable.getTableName().getSchemaToken() == null)&&(TSQLEnv.supportSchema(this.dbvendor))){
1812//                       // let find schema name for this table in env
1813//                       TSQLTable t = getSqlEnv().searchTable(".."+lcTable.getFullName());
1814//                       if (t != null){
1815//                           TSQLSchema s = t.getSchema();
1816//                           if (s != null){
1817//                               lcTable.getTableName().setSchemaToken(new TSourceToken(s.getName()),true);
1818//                           }
1819//                       }
1820//                   }
1821
1822               }
1823               // let's check is it cte name or ordinary table name
1824               TCTEList lcCteList = searchCTEList(false);
1825               TCTE lcCte = null;
1826               if (lcCteList != null){
1827                 for(int i=0;i<lcCteList.size();i++){
1828                    lcCte = lcCteList.getCTE(i);
1829                    if (lcCte.getTableName().toString().compareToIgnoreCase(TBaseType.getTextWithoutQuoted(lcTable.getTableName().toString()))==0){
1830                        // this is cte name
1831                        if (lcTable.setCTE(lcCte)){
1832                            lcTable.setCTEName(true);
1833                            lcTable.setCteColomnReferences(lcCte.getColumnList());
1834                            break;
1835                        }
1836                    }
1837                 }
1838               }
1839
1840               break;
1841           }
1842           case tableExpr:{
1843               ESqlClause location =  ESqlClause.tableExpr; //ESqlClause.resultColumn;
1844               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1845                   // change location here
1846               }
1847               lcTable.setTableExpr(pfromTable.getTableExpr());
1848               lcTable.getTableExpr().doParse(this,location);
1849               // teradata: SELECT table1.* FROM table(strtok_split_to_table(1, 'dm-calcite-raven/td/bq', '-') RETURNS (outkey integer, tokennum integer, token varchar(20)) ) as table1;
1850               // RETURNS (outkey integer, tokennum integer, token varchar(20))
1851               lcTable.setColumnDefinitions(pfromTable.getColumnDefinitions());
1852               // Teradata table function HASH BY and LOCAL ORDER BY clauses
1853               lcTable.setHashByClause(pfromTable.getHashByClause());
1854               lcTable.setLocalOrderBy(pfromTable.getLocalOrderBy());
1855               break;
1856           }
1857           case subquery:{
1858//               if (pfromTable.getSubquerynode().isHiveFromQuery()){
1859//                   THiveFromQuery fromQuery  = new THiveFromQuery(dbvendor);
1860//                   lcTable.setHiveFromQuery(fromQuery);
1861//                   fromQuery.rootNode = pfromTable.getSubquerynode();
1862//                   fromQuery.setStartToken(pfromTable.getSubquerynode());
1863//                   fromQuery.setEndToken(pfromTable.getSubquerynode());
1864//                   fromQuery.setLabelName(this.labelName);
1865//                   fromQuery.doParseStatement(this);
1866//               }else{
1867//                   lcTable.subquery = new TSelectSqlStatement(dbvendor);
1868//                   lcTable.subquery.rootNode = pfromTable.getSubquerynode();
1869//                   lcTable.subquery.setLocation(ESqlClause.elTable);
1870//                   //lcTable.subquery.resultColumnList = ((TSelectSqlNode)lcTable.subquery.rootNode).getResultColumnList();
1871//                   lcTable.subquery.doParseStatement(this);
1872//               }
1873
1874               lcTable.subquery = new TSelectSqlStatement(dbvendor);
1875               lcTable.subquery.rootNode = pfromTable.getSubquerynode();
1876               if (pLocation == ESqlClause.unknown){
1877                   lcTable.subquery.setLocation(ESqlClause.elTable);
1878               }else{
1879                   lcTable.subquery.setLocation(pLocation);
1880               }
1881               //lcTable.subquery.resultColumnList = ((TSelectSqlNode)lcTable.subquery.rootNode).getResultColumnList();
1882               lcTable.subquery.doParseStatement(this);
1883
1884               break;
1885           }
1886           case function:{
1887               ESqlClause location = ESqlClause.tableFunction;// resultColumn;
1888               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1889                   // change location here
1890               }
1891               lcTable.setFuncCall(pfromTable.getFuncCall());
1892               lcTable.getFuncCall().doParse(this,location);
1893               break;
1894           }
1895           case containsTable:{
1896               ESqlClause location = ESqlClause.containsTable;//resultColumn;
1897               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1898                   // change location here
1899               }
1900               lcTable.setContainsTable(pfromTable.getContainsTable());
1901               lcTable.getContainsTable().doParse(this,location);
1902               break;
1903           }
1904
1905           case openrowset:{
1906               ESqlClause location = ESqlClause.openrowset;//resultColumn;
1907               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1908                   // change location here
1909               }
1910               lcTable.setOpenRowSet(pfromTable.getOpenRowSet());
1911               lcTable.getOpenRowSet().doParse(this,location);
1912               break;
1913           }
1914
1915           case openxml:{
1916               ESqlClause location = ESqlClause.openxml;//resultColumn;
1917               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1918                   // change location here
1919               }
1920               lcTable.setOpenXML(pfromTable.getOpenXML());
1921               lcTable.getOpenXML().doParse(this,location);
1922               break;
1923           }
1924
1925           case opendatasource:{
1926               ESqlClause location = ESqlClause.opendatasource;//resultColumn;
1927               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1928                   // change location here
1929               }
1930               lcTable.setOpenDatasource(pfromTable.getOpenDatasource());
1931               lcTable.getOpenDatasource().doParse(this,location);
1932               break;
1933           }
1934
1935           case openquery:{
1936               ESqlClause location = ESqlClause.openquery;//resultColumn;
1937               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1938                   // change location here
1939               }
1940               lcTable.setOpenquery(pfromTable.getOpenQuery());
1941               lcTable.getOpenquery().doParse(this,location);
1942               lcTable.setSubquery(lcTable.getOpenquery().getSubquery());
1943               break;
1944           }
1945
1946           case datachangeTable:{
1947               ESqlClause location = ESqlClause.datachangeTable;//resultColumn;
1948               if (sqlstatementtype == ESqlStatementType.sstinsert ){
1949                   // change location here
1950               }
1951               lcTable.setDatachangeTable(pfromTable.getDatachangeTable());
1952               lcTable.getDatachangeTable().doParse(this,location);
1953               break;
1954           }
1955           case rowList:{
1956               ESqlClause location = ESqlClause.rowList;//resultColumn;
1957               lcTable.setValueClause(pfromTable.getValueClause());
1958               lcTable.getValueClause().doParse(this,location);
1959               break;
1960           }
1961           case pivoted_table:{
1962               ESqlClause location = ESqlClause.pivoted_table;//resultColumn;
1963               lcTable.getPivotedTable().doParse(this,location);
1964               addToTableList = false;
1965               targetTable = lcTable;
1966               break;
1967           }
1968           case xmltable:{
1969               ESqlClause location = ESqlClause.xmltable;//resultColumn;
1970               lcTable.setXmlTable(pfromTable.getXmlTable());
1971               lcTable.getXmlTable().doParse(this,location);
1972               break;
1973           }
1974
1975           case informixOuter:{
1976               ESqlClause location = ESqlClause.outerTable;//resultColumn;
1977               lcTable.setOuterClause(pfromTable.getOuterClause());
1978               lcTable.getOuterClause().doParse(this,location);
1979               break;
1980           }
1981
1982           case table_ref_list:{
1983               lcTable.setFromTableList(pfromTable.getFromTableList());
1984               break;
1985           }
1986//           case hiveFromQuery:{
1987//               THiveFromQuery fromQuery = new THiveFromQuery(EDbVendor.dbvhive);
1988//               fromQuery.rootNode = pfromTable.getFromQuerySqlNode();
1989//               fromQuery.doParseStatement(this);
1990//               lcTable.setHiveFromQuery(fromQuery);
1991//               break;
1992//           }
1993           case output_merge:{
1994               TMergeSqlStatement outputMerge = new TMergeSqlStatement(EDbVendor.dbvmssql);
1995               outputMerge.rootNode = pfromTable.getMergeSqlNode();
1996               outputMerge.doParseStatement(this);
1997               lcTable.setOutputMerge(outputMerge);
1998               break;
1999           }
2000           case td_unpivot:{
2001               // Set the TD_UNPIVOT output table before doParse so that VALUE_COLUMNS and
2002               // UNPIVOT_COLUMN can be linked to it (they are output columns of TD_UNPIVOT)
2003               pfromTable.getTdUnpivot().setTdUnpivotOutputTable(lcTable);
2004               pfromTable.getTdUnpivot().doParse(this,ESqlClause.tdUnPivot);
2005               lcTable.setTdUnpivot(pfromTable.getTdUnpivot());
2006               break;
2007           }
2008           case unnest:{
2009               pfromTable.getUnnestClause().doParse(this,ESqlClause.elTable);
2010               lcTable.setUnnestClause(pfromTable.getUnnestClause());
2011               if (lcTable.getAliasClause() != null){
2012                   if (lcTable.getAliasClause().getColumns() != null){
2013                       for(TObjectName pColumn:lcTable.getAliasClause().getColumns()){
2014                           lcTable.getLinkedColumns().addObjectName(pColumn);
2015                           pColumn.setSourceTable(lcTable);
2016                       }
2017                   }else if (lcTable.getAliasClause().getAliasName() != null){
2018//                       SELECT *
2019//                               FROM UNNEST(['foo', 'bar', 'baz', 'qux', 'corge', 'garply', 'waldo', 'fred']) AS element
2020//                       WITH OFFSET AS offset
2021
2022                       // add element as column of unnest table.
2023                       TObjectName newColumn = TObjectName.createObjectName(this.dbvendor,EDbObjectType.column,lcTable.getAliasClause().getAliasName().getStartToken());
2024                       lcTable.getLinkedColumns().addObjectName(newColumn);
2025                       newColumn.setSourceTable(lcTable);
2026                   }
2027               }
2028
2029               if (lcTable.getUnnestClause().getWithOffset() != null){
2030                   if (lcTable.getUnnestClause().getWithOffsetAlais() != null){
2031                       // with offset as offsetAlias
2032                       TAliasClause aliasClause = lcTable.getUnnestClause().getWithOffsetAlais();
2033                       if (aliasClause.getAliasName() != null){
2034                           TObjectName newColumn = TObjectName.createObjectName(this.dbvendor,EDbObjectType.column,aliasClause.getAliasName().getStartToken());
2035                           lcTable.getLinkedColumns().addObjectName(newColumn);
2036                           newColumn.setSourceTable(lcTable);
2037                       }
2038                   }else{
2039                       // with offset
2040                       TObjectName newColumn = TObjectName.createObjectName(this.dbvendor,EDbObjectType.column,new TSourceToken("offset"));
2041                       lcTable.getLinkedColumns().addObjectName(newColumn);
2042                       newColumn.setSourceTable(lcTable);
2043                   }
2044               }
2045
2046               // link columns in the select list to unnest()
2047               // select emp_id,name,state,city,zipcode from `absolute-runner-302907.gudu_sqlflow.ADDRESS_NESTED`, UNNEST(address)
2048               if (lcTable.getUnnestClause().getDerivedColumnList() != null){
2049                   TObjectNameList derivedColumns = lcTable.getUnnestClause().getDerivedColumnList();
2050                   for(int i=0;i<derivedColumns.size();i++){
2051                       //System.out.println(derivedColumns.getObjectName(i).toString());
2052                       lcTable.getLinkedColumns().addObjectName(derivedColumns.getObjectName(i));
2053                   }
2054               }
2055
2056               break;
2057           }
2058           case jsonTable:{
2059               ESqlClause location = ESqlClause.jsonTable;//resultColumn;
2060               lcTable.setJsonTable(pfromTable.getJsonTable());
2061               lcTable.getJsonTable().doParse(this,location);
2062               break;
2063           }
2064           case externalTable:
2065               lcTable.setTableName(pfromTable.getTableObjectName());
2066               lcTable.setColumnDefinitions(pfromTable.getColumnDefinitions());
2067               lcTable.getColumnDefinitions().doParse(this,pLocation);
2068               lcTable.setTableType(ETableSource.externalTable); // tableType is reset in setTableName() method, so we reset it here
2069               break;
2070           case caseJoin:
2071               lcTable.setCaseJoin(pfromTable.getCaseJoin());
2072               lcTable.getCaseJoin().doParse(this,pLocation);
2073               break;
2074           case stageReference:
2075               lcTable.setStageReference(pfromTable.getStageReference());
2076               lcTable.getStageReference().doParse(this,pLocation);
2077
2078               lcTable.setTableName(lcTable.getStageReference().getStageName());
2079               lcTable.setTableType(ETableSource.stageReference);
2080               lcTable.getTableName().setSqlEnv(getSqlEnv());
2081
2082               break;
2083
2084      }//switch
2085
2086//        if (pfromTable.getPivotClause() != null){
2087//            lcTable.setPivotClause(pfromTable.getPivotClause());
2088//            lcTable.getPivotClause().doParse(this,ESqlClause.resultColumn);
2089//        }
2090
2091        if (lcTable.getTableOperatorPartitionBy() != null) {
2092            lcTable.getTableOperatorPartitionBy().doParse(this, ESqlClause.unknown);
2093        }
2094        if (lcTable.getTableOperatorOrderBy() != null) {
2095            lcTable.getTableOperatorOrderBy().doParse(this, ESqlClause.unknown);
2096        }
2097        lcTable.setPartitionExtensionClause(pfromTable.getPartitionExtensionClause());
2098
2099        //tables.addTable(lcTable);
2100        if (addToTableList) {
2101            addToTables(lcTable);
2102        }
2103
2104        // AS OF (SELECT ...) (Oracle flashback, Teradata temporal) and AT(TIMESTAMP => (SELECT ...)) (Snowflake time
2105        // travel): the point in time is an expression whose subqueries are statements of their own (Mantis 4920)
2106        TExpression pointInTime = lcTable.getPointInTime();
2107        if (pointInTime != null) {
2108            pointInTime.doParse(this, ESqlClause.unknown);
2109        }
2110
2111        if (lcTable.getTableHintList() != null){
2112            for(int i=0;i<lcTable.getTableHintList().size();i++){
2113                TTableHint hint = lcTable.getTableHintList().getElement(i);
2114                hint.setOwnerTable(lcTable);
2115                hint.doParse(this,ESqlClause.tableHint);
2116            }
2117        }
2118
2119        if (lcTable.getLateralViewList() != null){
2120            for(TLateralView lateralView:lcTable.getLateralViewList()){
2121                TTable t = lateralView.createATable(this);
2122                addToTables(t);
2123                this.relations.add(t);
2124            }
2125        }
2126
2127        if (lcTable.getAliasClause() != null){
2128            if (lcTable.getAliasClause().toString().equalsIgnoreCase("and")){
2129                // end keyword can't be alias name
2130                TSourceToken st1 = lcTable.getAliasClause().getStartToken();
2131                TSyntaxError err = new TSyntaxError(st1.toString()
2132                        ,st1.lineNo,st1.columnNo
2133                        ,String.format("AND keyword can't be table alias")
2134                        ,EErrorType.sperror
2135                        ,TBaseType.MSG_ERROR_AND_KEYWORD_CANT_USED_AS_TABLE_ALIAS
2136                        ,this,st1.posinlist);
2137                this.parseerrormessagehandle( err);
2138
2139            }
2140        }
2141
2142        return lcTable;
2143    }
2144
2145   public TJoin analyzeJoin(TJoinExpr pJoinExpr,TJoin pJoin,Boolean isSub){
2146        TJoin retval = pJoin;
2147        TJoinItem lcJoinItem = null ;
2148
2149        if (pJoinExpr == null) {return retval;}
2150
2151        if (pJoinExpr.getJointype() == EJoinType.nested)
2152        {
2153            if (isSub)
2154            {
2155                if (retval == null) {  // top level, left side is a join
2156                  retval = new TJoin();
2157                  retval.setStartToken(pJoinExpr.getStartToken());
2158                  retval.setEndToken(pJoinExpr.getEndToken());
2159                }
2160
2161                pJoinExpr.setJointype(pJoinExpr.original_jontype);
2162                retval.setJoin(analyzeJoin(pJoinExpr,null,true));
2163                //retval =analyzeJoin(pJoinExpr,null,true);
2164                retval.setKind(TBaseType.join_source_join);
2165                retval.getJoin().setAliasClause(pJoinExpr.getAliasClause());
2166                retval.getJoin().setWithParen(true);
2167                retval.getJoin().setNestedParen(pJoinExpr.getNestedParen());
2168            }
2169           else
2170            {
2171                if (retval == null)
2172                {
2173                    retval = new TJoin();
2174                    retval.setStartToken(pJoinExpr.getStartToken());
2175                    retval.setEndToken(pJoinExpr.getEndToken());
2176                    retval.setGsqlparser(this.getGsqlparser());
2177                }
2178                else
2179                {
2180                }
2181                pJoinExpr.setJointype(pJoinExpr.original_jontype);
2182                retval = analyzeJoin(pJoinExpr,retval,false);
2183                //retval.setJoin(analyzeJoin(pJoinExpr,retval,false));
2184                //retval = analyzeJoin(pJoinExpr,retval,false);
2185                //retval.setKind(TBaseType.join_source_join);
2186                //retval.setKind(TBaseType.join_source_table);
2187                //retval.setAliasClause(pJoinExpr.getAliasClause());
2188                retval.setAliasClause(pJoinExpr.getAliasClause());
2189                retval.setWithParen(true);
2190                retval.setNestedParen(pJoinExpr.getNestedParen());
2191            }
2192            return retval;
2193        }
2194
2195        if (pJoinExpr.getLeftOperand().getFromtableType() != ETableSource.join){
2196            if (retval == null) {
2197              retval = new TJoin();
2198              retval.setStartToken(pJoinExpr.getStartToken());
2199              retval.setEndToken(pJoinExpr.getEndToken());
2200              retval.setGsqlparser(this.getGsqlparser());
2201
2202              //  retval.setStartToken(pJoinExpr.getLeftOperand().getStartToken());
2203              //  retval.setEndToken(pJoinExpr.getLeftOperand().getEndToken());
2204            }
2205            TTable lcTable = analyzeFromTable(pJoinExpr.getLeftOperand(),true,ESqlClause.join);
2206            lcTable.setEffectType(ETableEffectType.tetSelect);
2207            retval.setTable(lcTable);
2208            //retval.joinTable.OwnerJoin = result;
2209            retval.setKind(TBaseType.join_source_table);
2210            pJoinExpr.setLeftTable(lcTable);
2211        }else{
2212            TJoinExpr lcJoinItemJoinExpr = pJoinExpr.getLeftOperand().getJoinExpr();
2213            //if (lcJoinItemJoinExpr.getJointype() == TBaseType.join_nested){
2214            //    lcJoinItemJoinExpr.setJointype(lcJoinItemJoinExpr.original_jontype);
2215            //}
2216
2217            if (retval != null) {
2218              retval = analyzeJoin(lcJoinItemJoinExpr,retval,true);
2219            } else {
2220              retval = analyzeJoin(lcJoinItemJoinExpr,retval,isSub);
2221            }
2222            retval.setStartToken(lcJoinItemJoinExpr.getStartToken());
2223            retval.setEndToken(lcJoinItemJoinExpr.getEndToken());
2224
2225
2226            TTable lcTable = new TTable();
2227            lcTable.setTableType(pJoinExpr.getLeftOperand().getFromtableType());
2228            lcTable.setAliasClause(lcJoinItemJoinExpr.getAliasClause());
2229            lcTable.setStartToken(lcJoinItemJoinExpr.getStartToken());
2230            lcTable.setEndToken(lcJoinItemJoinExpr.getEndToken());
2231            pJoinExpr.setLeftTable(lcTable);
2232            lcTable.setJoinExpr(lcJoinItemJoinExpr);
2233        }
2234
2235        if (pJoinExpr.getRightOperand().getFromtableType() != ETableSource.join){
2236            if (retval != null)
2237            {
2238                lcJoinItem = new TJoinItem();
2239                TTable lcTable = analyzeFromTable(pJoinExpr.getRightOperand(),true,ESqlClause.join);
2240                lcTable.setEffectType(ETableEffectType.tetSelect);
2241                lcJoinItem.setTable(lcTable);
2242                lcJoinItem.setStartToken(lcJoinItem.getTable().getStartToken());
2243                lcJoinItem.setEndToken(lcJoinItem.getTable().getEndToken());
2244               // lcJoinItem.JoinItemTable.OwnerJoinItem := lcJoinItem;
2245                lcJoinItem.setKind(TBaseType.join_source_table);
2246                retval.getJoinItems().addJoinItem(lcJoinItem);
2247                pJoinExpr.setRightTable(lcTable);
2248            }
2249        }else{
2250            if (retval != null)
2251            {
2252                lcJoinItem = new TJoinItem();
2253                lcJoinItem.setKind(TBaseType.join_source_join);
2254                TJoinExpr lcJoinItemJoinExpr = pJoinExpr.getRightOperand().getJoinExpr();
2255                //if (lcJoinItemJoinExpr.getJointype() == TBaseType.join_nested){
2256                //    lcJoinItemJoinExpr.setJointype(lcJoinItemJoinExpr.original_jontype);
2257                //}
2258                lcJoinItem.setJoin(analyzeJoin(pJoinExpr.getRightOperand().getJoinExpr(),null,false));
2259                lcJoinItem.getJoin().setAliasClause(lcJoinItemJoinExpr.getAliasClause());
2260                lcJoinItem.setStartToken(lcJoinItem.getJoin().getStartToken());
2261                lcJoinItem.setEndToken(lcJoinItem.getJoin().getEndToken());
2262                retval.getJoinItems().addJoinItem(lcJoinItem);
2263
2264                TTable lcTable = new TTable();
2265                lcTable.setTableType(pJoinExpr.getRightOperand().getFromtableType());
2266                lcTable.setAliasClause(lcJoinItemJoinExpr.getAliasClause());
2267                lcTable.setStartToken(lcJoinItemJoinExpr.getStartToken());
2268                lcTable.setEndToken(lcJoinItemJoinExpr.getEndToken());
2269                pJoinExpr.setRightTable(lcTable);
2270                lcTable.setJoinExpr(lcJoinItemJoinExpr);
2271            }
2272        }
2273
2274        if (lcJoinItem == null) return retval;
2275
2276        lcJoinItem.setJoinType(pJoinExpr.getJointype());
2277        lcJoinItem.setUsingColumns(pJoinExpr.usingColumns);
2278        if ((lcJoinItem.getUsingColumns() != null) && (tables.size()>1)){
2279            TObjectName crf ;
2280            for (int i=0;i<lcJoinItem.getUsingColumns().size();i++){
2281                crf = lcJoinItem.getUsingColumns().getObjectName(i);
2282                // link this column to last 2 tables
2283                tables.getTable(tables.size()-1).getObjectNameReferences().addObjectName(crf);
2284                tables.getTable(tables.size()-2).getObjectNameReferences().addObjectName(crf);
2285
2286                tables.getTable(tables.size()-1).getLinkedColumns().addObjectName(crf);
2287                crf.setSourceTable(tables.getTable(tables.size()-1));
2288                tables.getTable(tables.size()-2).getLinkedColumns().addObjectName(crf);
2289                crf.setSourceTable(tables.getTable(tables.size()-2));
2290
2291            }
2292            lcJoinItem.setEndToken(lcJoinItem.getUsingColumns().getEndToken());
2293        }
2294        lcJoinItem.setOnCondition(pJoinExpr.onCondition);
2295        if (lcJoinItem.getOnCondition() != null)
2296        {
2297            lcJoinItem.getOnCondition().doParse(this,ESqlClause.joinCondition);
2298            lcJoinItem.setEndToken(lcJoinItem.getOnCondition().getEndToken());
2299        }
2300
2301
2302        return retval;
2303    }
2304
2305    public boolean locateVariableOrParameter(TObjectName cr){
2306        return locateVariableOrParameter(cr,false);
2307    }
2308
2309    /**
2310     * Whether this statement is MySQL SQL: {@link EDbVendor#dbvmysql}, or {@link EDbVendor#dbvoceanbase} in a MySQL or
2311     * system tenant ({@link TGSqlParser#getOBTenantMode()}).
2312     *
2313     * @return true for MySQL SQL
2314     */
2315    public boolean isMySQLDialect(){
2316        return isMySQLDialect(dbvendor, getGsqlparser());
2317    }
2318
2319    /**
2320     * Whether SQL of {@code vendor} parsed by {@code parser} is MySQL SQL: {@link EDbVendor#dbvmysql}, or
2321     * {@link EDbVendor#dbvoceanbase} in a MySQL or system tenant. For code that holds a node rather than a statement
2322     * (resolver2's ScopeBuilder, TObjectName), which reaches the parser through {@code getGsqlparser()}.
2323     *
2324     * @param vendor the SQL's vendor
2325     * @param parser the parser that produced it, or null when unknown (read as a MySQL tenant)
2326     * @return true for MySQL SQL
2327     */
2328    public static boolean isMySQLDialect(EDbVendor vendor, TGSqlParser parser){
2329        if (vendor == EDbVendor.dbvmysql) return true;
2330        if (vendor != EDbVendor.dbvoceanbase) return false;
2331        return (parser == null) || (parser.getOBTenantMode() != EOBTenantMode.ORACLE);
2332    }
2333
2334    public boolean locateVariableOrParameter(TObjectName cr, boolean checkVariableDeclaredInProcedure){
2335      boolean ret =  false;
2336      if (cr.getDbObjectType() == EDbObjectType.variable) return true;
2337      if (cr.toString().equalsIgnoreCase("*")) return  false;
2338      //search variable in framestack
2339
2340      TVariable symbolVariable = null;
2341
2342      if (cr.getTableToken() != null){
2343          // record_variable.column
2344          symbolVariable =  TSymbolTableManager.searchSymbolVariable(this.getFrameStack(),cr.getTableToken().toString());
2345          if (symbolVariable != null){
2346              cr.getTableToken().setDbObjectType(EDbObjectType.variable);
2347              //TTable sourceTable = new TTable(new TObjectName(EDbObjectType.table,symbolVariable.getVariableName().getStartToken()));
2348              TTable sourceTable = new TTable(TObjectName.createObjectName (this.dbvendor, EDbObjectType.variable,symbolVariable.getVariableName().getStartToken()));
2349              sourceTable.getLinkedColumns().addObjectName(cr);
2350              cr.setSourceTable(sourceTable);
2351             // symbolVariable.getVariableName().getReferencedObjects().addObjectName(cr);
2352             // System.out.println("find variable:"+cr.toString());
2353              cr.setResolveStatus(TBaseType.RESOLVED_AND_FOUND); // set resolve status to resolved,避免在 TAttributeResolver 中关联到其他 table
2354              return true;
2355          }
2356
2357      }else{
2358          // variable
2359          symbolVariable =  TSymbolTableManager.searchSymbolVariable(this.getFrameStack(),cr.toString());
2360          if (symbolVariable != null){
2361              cr.setDbObjectType(EDbObjectType.variable);
2362              symbolVariable.getVariableName().getReferencedObjects().addObjectName(cr);
2363              return true;
2364          }
2365      }
2366
2367        // check parameters in plsql and MySQL routines only, may add support for sql server later.
2368        // MySQL includes an OceanBase MySQL/system tenant (Mantis 4813): a DECLAREd variable of the routine is read
2369        // as that variable there too, also when a table of the statement has a column of the same name.
2370      if(! ((dbvendor == EDbVendor.dbvoracle)||isMySQLDialect())) return false;
2371      if (cr.getObjectType() == TObjectName.ttobjVariable) return true;
2372
2373        Stack symbolTable = this.getTopStatement().getSymbolTable();
2374        TSymbolTableItem item = null;
2375        TObjectName objName = null;
2376        TObjectName qualifiedName = null; // function/procedure name or label name of plsql block
2377        for (int i = symbolTable.size()-1;i>=0;i--){
2378            item = (TSymbolTableItem)symbolTable.get(i);
2379            if (item.getData() instanceof TParameterDeclaration){
2380                objName = ((TParameterDeclaration)item.getData()).getParameterName();
2381            }else if (item.getData() instanceof TVarDeclStmt){
2382                objName = ((TVarDeclStmt)item.getData()).getElementName();
2383            }else  if (item.getData() instanceof TObjectName){
2384                objName = (TObjectName)item.getData();
2385            }
2386
2387            if (objName == null) continue;
2388
2389            // strip quote characters so a quoted declaration ("P_DATE" IN NUMBER)
2390            // still matches an unquoted reference P_DATE (MantisBT 4504)
2391            if (TBaseType.getTextWithoutQuoted(cr.toString())
2392                    .compareToIgnoreCase(TBaseType.getTextWithoutQuoted(objName.toString())) == 0){
2393                ret = true;
2394                if (checkVariableDeclaredInProcedure) break; // return true if variable declared in procedure
2395                for(int j=0;i<tables.size();i++){
2396                    TTable lcTable = tables.getTable(j);
2397                    if (lcTable.isBaseTable()){
2398                        if (fireOnMetaDatabaseTableColumn(
2399                                             lcTable.getPrefixServer()
2400                                            ,lcTable.getPrefixDatabase()
2401                                            ,lcTable.getPrefixSchema()
2402                                            ,lcTable.getName()
2403                                            ,cr.getColumnNameOnly())){
2404                            ret = false;
2405                            break;
2406                        }
2407                    }
2408                }
2409
2410                if (ret)  break;
2411            }else if (cr.toString().indexOf(".")>0){
2412                // qualified object reference, compare it with procedure/function/block label prefixed
2413                if (item.getStmt() instanceof TPlsqlCreateFunction){
2414                   qualifiedName = ((TPlsqlCreateFunction)item.getStmt()).getFunctionName();
2415                }else if (item.getStmt() instanceof TPlsqlCreateProcedure){
2416                   qualifiedName = ((TPlsqlCreateProcedure)item.getStmt()).getProcedureName();
2417                }else if (item.getStmt() instanceof TCommonBlock){
2418                   qualifiedName = ((TCommonBlock)item.getStmt()).getLabelName();
2419                }
2420
2421                if (qualifiedName != null){
2422                    if (TBaseType.getTextWithoutQuoted(cr.toString()).compareToIgnoreCase(
2423                            TBaseType.getTextWithoutQuoted(qualifiedName.toString()) + '.'
2424                                    + TBaseType.getTextWithoutQuoted(objName.toString())) == 0){
2425                        ret = true;
2426                    }
2427                }
2428
2429                if (ret ) break;
2430            }
2431
2432        }
2433          if (ret){
2434              //add this parameter or variable reference to original parameter/variable
2435                  objName.getReferencedObjects().addObjectName(cr);
2436                  cr.setObjectType(TObjectName.ttobjVariable);
2437          }
2438          return ret;
2439    }
2440
2441    TCTE findCTEByName(String cteName){
2442        TCTEList lcCteList = searchCTEList(false);
2443        TCTE lcCte = null;
2444        if (lcCteList != null){
2445          for(int i=0;i<lcCteList.size();i++){
2446             if (lcCteList.getCTE(i).getTableName().toString().compareToIgnoreCase(cteName)==0){
2447                 lcCte = lcCteList.getCTE(i);
2448                 break;
2449             }
2450          }
2451        }
2452     return lcCte;
2453    }
2454
2455    /**
2456     * @deprecated since 2.3.8.2, use {@link TTable#getExpandedStarColumns()} instead.
2457     *
2458     * @param lcTable
2459     * @return
2460     */
2461    public ArrayList<String> getColumnsInTable(TTable lcTable){
2462        if (lcTable.isCTEName()){
2463            ArrayList<String> columns = new ArrayList<>();
2464            if (lcTable.getCteColomnReferences()!=null){
2465                for(TObjectName n:lcTable.getCteColomnReferences()){
2466                    columns.add(n.toString());
2467                }
2468            }else if (lcTable.getCTE().getSubquery() != null && lcTable.getCTE().getSubquery().getResultColumnList() != null){
2469                for(TResultColumn resultColumn:lcTable.getCTE().getSubquery().getResultColumnList()){
2470                    columns.add(resultColumn.getDisplayName());
2471                }
2472            }
2473            return columns;
2474        }else{
2475            return getColumnsInTable(
2476                    lcTable.getPrefixServer()
2477                    ,lcTable.getPrefixDatabase()
2478                    ,lcTable.getPrefixSchema()
2479                    ,lcTable.getName()
2480            );
2481        }
2482    }
2483
2484
2485    /**
2486     * @deprecated since 2.3.8.2, use {@link TTable#getExpandedStarColumns()} instead.
2487     *
2488     * @param pServer
2489     * @param pDatabase
2490     * @param pSchema
2491     * @param pTable
2492     * @return
2493     */
2494    public ArrayList<String> getColumnsInTable(String pServer,String pDatabase,String pSchema,String pTable){
2495        TFrame stackFrame = getFrameStack().firstElement();
2496        TGlobalScope globalScope = (TGlobalScope)stackFrame.getScope();
2497
2498        if (globalScope.getSqlEnv() != null){
2499            return globalScope.getSqlEnv().getColumnsInTable(pDatabase+"."+pSchema+"."+pTable,false);
2500        }else{
2501            return  null;
2502        }
2503    }
2504
2505
2506    public boolean fireOnMetaDatabaseTableColumn(String pServer,String pDatabase,String pSchema,String pTable,String pColumn){
2507//        boolean lcResult = false;
2508//        if (this.getGsqlparser().getMetaDatabase() != null){
2509//            lcResult = this.getGsqlparser().getMetaDatabase().checkColumn(pServer,pDatabase,pSchema,pTable,pColumn);
2510//        }
2511
2512        TFrame stackFrame = getFrameStack().firstElement();
2513        TGlobalScope globalScope = (TGlobalScope)stackFrame.getScope();
2514
2515        // A table the script declared before this statement, which the catalog gets only after the parse (Mantis 4797).
2516        TSQLTable declared = ParseTimeCatalog.searchTable(this, pDatabase, pSchema, pTable);
2517        if (declared != null) return declared.searchColumn(pColumn);
2518
2519        if (globalScope.getSqlEnv() != null){
2520           // System.out.println(globalScope.getSqlEnv().toString());
2521
2522            return globalScope.getSqlEnv().columnInTable(pDatabase+"."+pSchema+"."+pTable,pColumn);
2523        }else{
2524            return  false;
2525        }
2526
2527//        return lcResult;
2528    }
2529
2530    /**
2531     * The tables of this statement after the one at {@code index}, which the catalog gives the unqualified column
2532     * {@code pColumn}, that the catalog gives it too: with one of them the name is ambiguous. Empty when a join merges
2533     * the columns of that name (USING it, or NATURAL). A reference to the same table under the same name is the same
2534     * relation, as the target of an UPDATE or DELETE that its FROM names again.
2535     */
2536    private List<TTable> otherTablesWithColumn(TObjectName pColumn, int index){
2537        List<TTable> lcOthers = null;
2538        TTable lcTable = tables.getTable(index);
2539        TSQLTable lcFirst = null;
2540        for (int j = index + 1; j < tables.size(); j++){
2541            TTable lcOther = tables.getTable(j);
2542            if ((!lcOther.isBaseTable()) || (lcOther.getEffectType() == ETableEffectType.tetSelectInto)
2543                    || (lcOther.getEffectType() == ETableEffectType.tetImplicitLateralDerivedTable)) continue;
2544            if (!fireOnMetaDatabaseTableColumn(lcOther.getPrefixServer(), lcOther.getPrefixDatabase(),
2545                    lcOther.getPrefixSchema(), lcOther.getName(), pColumn.getColumnNameOnly())) continue;
2546            if (lcFirst == null) lcFirst = metadataTableOf(lcTable);
2547            if ((lcFirst != null) && (lcFirst == metadataTableOf(lcOther)) && SQLUtil.compareIdentifier(dbvendor,
2548                    ESQLDataObjectType.dotTable, exposedName(lcOther), exposedName(lcTable))) continue;
2549            if (lcOthers == null) lcOthers = new ArrayList<>();
2550            lcOthers.add(lcOther);
2551        }
2552        if ((lcOthers == null) || mergesJoinColumn(pColumn)) return Collections.emptyList();
2553        return lcOthers;
2554    }
2555
2556    /**
2557     * Whether this is a SELECT written as a derived table in a FROM clause, LATERAL or not, as resolver2 tells it from
2558     * a subquery in an expression ({@code SelectScope.isCorrelated()}): where its own tables may have a name, the
2559     * name is theirs.
2560     */
2561    private boolean isDerivedTableInFrom(){
2562        if (!(this instanceof TSelectSqlStatement)) return false;
2563        ESqlClause lcLocation = ((TSelectSqlStatement) this).getLocation();
2564        return (lcLocation == ESqlClause.join) || (lcLocation == ESqlClause.elTable);
2565    }
2566
2567    /** The name a statement reads {@code table} by: its alias, else its own name. */
2568    private static String exposedName(TTable table){
2569        return table.getAliasName().isEmpty() ? table.getName() : table.getAliasName();
2570    }
2571
2572    /**
2573     * Whether the catalog proves {@code table} has no column named like the unqualified {@code pColumn}: a base table
2574     * whose columns it all knows ({@link TSQLTable#hasAllColumnsKnown()}).
2575     */
2576    private boolean provablyLacksColumn(TTable table, TObjectName pColumn){
2577        if ((table == null) || (!table.isBaseTable()) || pColumn.isQualified()) return false;
2578        TSQLTable lcMetadata = metadataTableOf(table);
2579        return (lcMetadata != null) && lcMetadata.hasAllColumnsKnown()
2580                && (!lcMetadata.searchColumn(pColumn.getColumnNameOnly()));
2581    }
2582
2583    /** The catalog table whose columns {@link #fireOnMetaDatabaseTableColumn} reads for {@code table}, or null. */
2584    private TSQLTable metadataTableOf(TTable table){
2585        TSQLTable declared = ParseTimeCatalog.searchTable(this, table.getPrefixDatabase(), table.getPrefixSchema(),
2586                table.getName());
2587        if (declared != null) return declared;
2588        TSQLEnv lcEnv = ((getFrameStack() != null) && (!getFrameStack().isEmpty()))
2589                ? ((TGlobalScope) getFrameStack().firstElement().getScope()).getSqlEnv() : getSqlEnv();
2590        return (lcEnv == null) ? null
2591                : lcEnv.searchTable(table.getPrefixDatabase() + "." + table.getPrefixSchema() + "." + table.getName());
2592    }
2593
2594    /** Whether a join of this statement merges the columns named like {@code pColumn}: USING that name, or NATURAL. */
2595    private boolean mergesJoinColumn(TObjectName pColumn){
2596        Deque<TJoinExpr> lcPending = new ArrayDeque<>();
2597        for (TTable lcRelation : getRelations()){
2598            if ((lcRelation != null) && (lcRelation.getJoinExpr() != null)) lcPending.push(lcRelation.getJoinExpr());
2599        }
2600        while (!lcPending.isEmpty()){
2601            TJoinExpr lcJoin = lcPending.pop();
2602            if (mergesJoinColumn(lcJoin.getJointype(), lcJoin.getUsingColumns(), pColumn)) return true;
2603            if ((lcJoin.getLeftTable() != null) && (lcJoin.getLeftTable().getJoinExpr() != null)){
2604                lcPending.push(lcJoin.getLeftTable().getJoinExpr());
2605            }
2606            if ((lcJoin.getRightTable() != null) && (lcJoin.getRightTable().getJoinExpr() != null)){
2607                lcPending.push(lcJoin.getRightTable().getJoinExpr());
2608            }
2609        }
2610        if (joins == null) return false;
2611        for (int i = 0; i < joins.size(); i++){
2612            TJoin lcJoin = joins.getJoin(i);
2613            if ((lcJoin == null) || (lcJoin.getJoinItems() == null)) continue;
2614            for (int k = 0; k < lcJoin.getJoinItems().size(); k++){
2615                TJoinItem lcItem = lcJoin.getJoinItems().getJoinItem(k);
2616                if ((lcItem != null) && mergesJoinColumn(lcItem.getJoinType(), lcItem.getUsingColumns(), pColumn)) return true;
2617            }
2618        }
2619        return false;
2620    }
2621
2622    private boolean mergesJoinColumn(EJoinType joinType, TObjectNameList usingColumns, TObjectName pColumn){
2623        if (joinType != null){
2624            switch (joinType){
2625                case natural: case natural_full: case natural_fullouter: case natural_inner: case natural_left:
2626                case natural_right: case natural_leftouter: case natural_rightouter:
2627                    return true;
2628                default:
2629                    break;
2630            }
2631        }
2632        if (usingColumns == null) return false;
2633        for (int i = 0; i < usingColumns.size(); i++){
2634            TObjectName lcName = usingColumns.getObjectName(i);
2635            if ((lcName != null) && SQLUtil.compareIdentifier(dbvendor, ESQLDataObjectType.dotColumn,
2636                    lcName.getColumnNameOnly(), pColumn.getColumnNameOnly())) return true;
2637        }
2638        return false;
2639    }
2640
2641    public TTable getFirstPhysicalTable(){
2642        TTable ret = null;
2643        if (tables.size() == 0) return null;
2644        for(int i=0;i<tables.size();i++){
2645            if (tables.getTable(i).isBaseTable()) {
2646                ret = tables.getTable(i);
2647                break;
2648            }
2649        }
2650        return ret;
2651    }
2652    private TObjectNameList orphanColumns = null;
2653
2654    public TObjectNameList getOrphanColumns() {
2655        if (orphanColumns == null) orphanColumns = new TObjectNameList();
2656        return orphanColumns;
2657    }
2658
2659    protected boolean linkToFirstTable(TObjectName pColumn,int pCandidateTableCnt){
2660        boolean lcResult = false;
2661        // ClickHouse expression-CTE alias resolved by an outer scope during
2662        // the parent-walk: the reference is a scalar symbol — guessing it onto
2663        // this scope's first table would fabricate lineage (and did, when
2664        // metadata ruled the inner table out and the outer WITH alias won).
2665        if ((pColumn.getExpressionCteRef() != null) && (pColumn.getSourceTable() == null)){
2666            return true;
2667        }
2668        if ((dbvendor == EDbVendor.dbvteradata)&&(pColumn.isQualified())&&(pColumn.getTableToken().getDbObjectType() != EDbObjectType.subquery_alias)){
2669            // update table1 set col = 'value' where table1.id = table2.id2
2670            boolean isFoundLinkedTable = false;
2671            TCustomSqlStatement lcSql = this;
2672            while (lcSql != null){
2673                int i = 0;
2674                i = lcSql.tables.searchTableByNameOrAlias(pColumn.getTableToken().toString());
2675                isFoundLinkedTable = ( i != -1);
2676                if (isFoundLinkedTable) {
2677                    if (lcSql.tables.getTable(i).getEffectType() == ETableEffectType.tetImplicitLateralDerivedTable ){
2678                        // 如果不查重table,会导致 employee.first_name 中的 employee 被第二次加到 tables 中
2679//                        DELETE FROM foodmart.trimmed_employee ACT
2680//                        WHERE ACT.employee_id = employee.employee_id
2681//                        AND  employee.first_name = 'Walter'
2682//                        AND  trimmed_salary.employee_id = -1
2683
2684                        TTable newTable = lcSql.tables.getTable(i);
2685                        newTable.getLinkedColumns().addObjectName(pColumn);
2686                        pColumn.setSourceTable(newTable);
2687                        pColumn.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_TABLE_IN_OLD_ALGORITHM);
2688                    }
2689                    break;
2690                }
2691                lcSql = lcSql.getParentStmt();
2692            }
2693            if (!isFoundLinkedTable){
2694                TTable newTable = null;
2695                if (pColumn.getDatabaseToken() == null){
2696
2697                    //newTable = new TTable(new TObjectName(EDbObjectType.table,pColumn.getTableToken()));
2698                    newTable = new TTable(TObjectName.createObjectName (this.dbvendor, EDbObjectType.table,pColumn.getTableToken()));
2699                    newTable.setStartToken(pColumn.getTableToken());
2700                    newTable.setEndToken(pColumn.getTableToken());
2701                }else{
2702
2703                    //newTable = new TTable(new TObjectName(EDbObjectType.table,pColumn.getSchemaToken(), pColumn.getTableToken()));
2704                    newTable = new TTable(TObjectName.createObjectName (this.dbvendor,EDbObjectType.table,pColumn.getDatabaseToken(), pColumn.getTableToken()));
2705                    newTable.setStartToken(pColumn.getSchemaToken());
2706                    newTable.setEndToken(pColumn.getTableToken());
2707                }
2708
2709                newTable.setTableType(ETableSource.objectname);
2710                newTable.setEffectType(ETableEffectType.tetImplicitLateralDerivedTable);
2711                newTable.getLinkedColumns().addObjectName(pColumn);
2712                pColumn.setSourceTable(newTable);
2713                pColumn.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_TABLE_IN_OLD_ALGORITHM);
2714                this.addToTables(newTable);
2715
2716                // 2024 年
2717                // 不能加入到 relations 中,否则会导致 下面 SQL 中 star column 同时链接到 SPCOMM.L_FIXED_RATE_PLAN_REF, ipshare_ofccplv.cprof_d_period_dates_ref
2718                // 从而导致 本来不该有的歧义产生
2719
2720                // UPDATE b_rate_plan
2721                //FROM
2722                //(
2723                //SELECT * FROM SPCOMM.L_FIXED_RATE_PLAN_REF
2724                //WHERE rate_plan_ref_eff_dt<= ipshare_ofccplv.cprof_d_period_dates_ref.PERIOD
2725                //) AS ref
2726                //SET accs_fee = REF.accs_fee,
2727                //SVC_TYPE = REF.prod_grp_lvl_1,
2728                //rate_plan_lvl3 = REF.rate_plan_lvl_3,
2729                //prod_grp_lvl3 = REF.prod_grp_lvl_2
2730                //WHERE b_rate_plan.svc_type IS NULL
2731
2732                // 2025/2/25, v3.0.4.8
2733                // 需要加入到 relations 中,新的 gudusoft.gsqlparser.resolver package 中的算法会处理这种情况
2734                // teradata 的隐式横向派生表不能加入到关系解析器中
2735                // this.getRelations().add(newTable);
2736            }
2737            return true;
2738        }
2739        if (pColumn.getCandidateTables().size() == 1){
2740            TTable table = pColumn.getCandidateTables().getTable(0);
2741            table.getLinkedColumns().addObjectName(pColumn);
2742            pColumn.setSourceTable(table);
2743            lcResult = true;
2744        }
2745        else if ((tables.size() == 1) || (pCandidateTableCnt == 1)){
2746            TTable table = tables.getTable(0);
2747
2748            if(table.getTableType() == ETableSource.function){
2749                //lcResult = linkToFunctionTable(table, pColumn);
2750                int iRet = table.getFuncCall().isColumnInThisTableFunction(this.getSqlEnv(),this.dbvendor,pColumn);
2751                if ( iRet == TBaseType.COLUMN_IN_TABEL_FUNCTION_YES){
2752                    lcResult = true;
2753                    table.getLinkedColumns().addObjectName(pColumn);
2754                    pColumn.setSourceTable(table);
2755                    lcResult = true;
2756                }else if ( iRet == TBaseType.COLUMN_IN_TABEL_FUNCTION_NO){
2757                    lcResult = false;
2758                }else{
2759                    table.getLinkedColumns().addObjectName(pColumn);
2760                    pColumn.setSourceTable(table);
2761                    lcResult = true;
2762                }
2763            }else if(table.getTableType() == ETableSource.subquery){
2764                if (! table.getSubquery().searchColumnInResultSet(pColumn,(tables.size()==1))){
2765                    getOrphanColumns().addObjectName(pColumn);
2766                    pColumn.setOrphanColumn(true);
2767                    pColumn.setOwnStmt(this);
2768                    TSourceToken st = pColumn.getStartToken();
2769                    if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
2770                        TBaseType.log(String.format("Add orphan column <%s> to statement in old algorithm in subquery %s",pColumn.toString(),table.getAliasName()),TLog.WARNING,table);
2771                    }
2772                    this.parseerrormessagehandle(new TSyntaxError(st.getAstext(), st.lineNo, st.columnNo
2773                            ,"find orphan column", EErrorType.sphint
2774                            , TBaseType.MSG_HINT_FIND_ORPHAN_COLUMN,this,st.posinlist,pColumn));
2775                }
2776            }
2777            else{
2778                table.getLinkedColumns().addObjectName(pColumn);
2779                pColumn.setSourceTable(table);
2780                lcResult = true;
2781                if ((dbvendor == EDbVendor.dbvbigquery)&&(pCandidateTableCnt == 0) && (pColumn.isQualified())){
2782                    // bigquery struct column used in query
2783//                    create view test as (SELECT rollNo,
2784//                            info.name as n1,
2785//                    info2.name as n2,
2786//                            info.age from my_first_dataset.student_records);
2787
2788                    pColumn.columnToProperty();
2789                }
2790            }
2791        }else if (tables.size() > 1){
2792            // if there is only a table without table alias, then, link to this table
2793            boolean foundOnlyOneTable = false;
2794            TTable tableWithoutAlias = null;
2795            for(TTable table:tables){
2796                if (table.isCTEName()) continue; // CTE 即便没有 指定alias,也不作为考虑对象
2797                if (table.getAliasClause() == null){
2798                    tableWithoutAlias = table;
2799                    if (foundOnlyOneTable){
2800                        foundOnlyOneTable = false;
2801                        break;
2802                    }else{
2803                        foundOnlyOneTable = true;
2804                    }
2805                }
2806            }
2807
2808            // A guess among several tables never takes one the catalog proves has no such column (Mantis 4797).
2809            if (foundOnlyOneTable && !provablyLacksColumn(tableWithoutAlias, pColumn)){
2810                tableWithoutAlias.getLinkedColumns().addObjectName(pColumn);
2811                pColumn.setSourceTable(tableWithoutAlias);
2812                lcResult = true;
2813            }else{
2814                getOrphanColumns().addObjectName(pColumn);
2815                pColumn.setOrphanColumn(true);
2816                pColumn.setOwnStmt(this);
2817                TSourceToken st = pColumn.getStartToken();
2818                if (TBaseType.DUMP_RESOLVER_LOG_TO_CONSOLE){
2819                    TBaseType.log(String.format("Add orphan column <%s> to statement in old algorithm ",pColumn.toString()),TLog.WARNING,this);
2820                }
2821
2822                this.parseerrormessagehandle(new TSyntaxError(st.getAstext(), st.lineNo, st.columnNo
2823                        ,"find orphan column", EErrorType.sphint
2824                        , TBaseType.MSG_HINT_FIND_ORPHAN_COLUMN,this,st.posinlist,pColumn));
2825            }
2826
2827        }
2828        return  lcResult;
2829    }
2830
2831    private boolean linkToFunctionTable(TTable table, TObjectName pColumn) {
2832        if(table.getTableName().toString().toUpperCase(java.util.Locale.ROOT).equals("STRING_SPLIT")){
2833            if(pColumn.getColumnNameOnly().toUpperCase(java.util.Locale.ROOT).equals("VALUE")){
2834                table.getLinkedColumns().addObjectName(pColumn);
2835                pColumn.setSourceTable(table);
2836                return true;
2837            }
2838            else return false;
2839        }
2840        else {
2841            table.getLinkedColumns().addObjectName(pColumn);
2842            pColumn.setSourceTable(table);
2843            return true;
2844        }
2845    }
2846
2847
2848    private TTable findInsertedOrDeleteTable(TTable table) {
2849        if (table == null) return null;
2850        
2851        // Check if this table is 'inserted' by examining both name and toString()
2852        // The inserted table might have different representations
2853        String tableName = table.getName();
2854        String tableString = table.toString();
2855        
2856        if ("inserted".equalsIgnoreCase(tableName) || 
2857            "inserted".equalsIgnoreCase(tableString) ||
2858            "deleted".equalsIgnoreCase(tableName) ||
2859            "deleted".equalsIgnoreCase(tableString)) {
2860            return table;
2861        }
2862        
2863        // If this is a join table, recursively check its components
2864        if (table.getTableType() == ETableSource.join && table.getJoinExpr() != null) {
2865            TJoinExpr joinExpr = table.getJoinExpr();
2866            
2867            // Check left side recursively
2868            TTable leftResult = findInsertedOrDeleteTable(joinExpr.getLeftTable());
2869            if (leftResult != null) {
2870                return leftResult;
2871            }
2872            
2873            // Check right side recursively  
2874            TTable rightResult = findInsertedOrDeleteTable(joinExpr.getRightTable());
2875            if (rightResult != null) {
2876                return rightResult;
2877            }
2878        }
2879        
2880        return null;
2881    }
2882   
2883
2884    boolean isSQLServerInsertedDelete(TObjectName pColumn){
2885        if (dbvendor != EDbVendor.dbvmssql) return false;
2886
2887        // only process sql server inserted delete column, if not then return false
2888       // if (!((pColumn.toString().toUpperCase().startsWith("INSERTED"))||(pColumn.toString().toUpperCase().startsWith("DELETED")))) return false;
2889        if (!((pColumn.getStartToken().tokencode == TBaseType.rrw_sqlserver_INSERTED )
2890                    ||(pColumn.getStartToken().tokencode == TBaseType.rrw_sqlserver_DELETED ))) return false;
2891
2892        // we need to get the target table in this statement's from clause which can be complex join, so we need to iterate all tables in this statement's from clause
2893        // to find the target table in the literal as 'inserted' or 'deleted'.
2894
2895        TTable lcTargetTable = null;
2896        for(TTable table : this.getRelations()){
2897            lcTargetTable = findInsertedOrDeleteTable(table);
2898            if (lcTargetTable != null){
2899                break;
2900            }
2901        }
2902
2903        if (lcTargetTable == null) return false;
2904
2905
2906        if (lcTargetTable.getLinkTable() != null){
2907            lcTargetTable.getLinkTable().getLinkedColumns().addObjectName(pColumn);
2908            pColumn.setSourceTable(lcTargetTable.getLinkTable());
2909
2910            pColumn.setResolveStatus(TBaseType.RESOLVED_AND_FOUND); // 避免在 // TAttributeResolver 中再次进行处理,关联到其他 table
2911
2912        }else{
2913            lcTargetTable.getLinkedColumns().addObjectName(pColumn);
2914            pColumn.setSourceTable(lcTargetTable);
2915            pColumn.setResolveStatus(TBaseType.RESOLVED_AND_FOUND); // 避免在 // TAttributeResolver 中再次进行处理,关联到其他 table
2916        }
2917        return true;
2918
2919    }
2920
2921    boolean isOracleNewOldTable(TObjectName pColumn){
2922        boolean ret = false;
2923        if (dbvendor != EDbVendor.dbvoracle) return false;
2924        if (!(pColumn.isQualified())) return false;
2925        if ((pColumn.getTableString().equalsIgnoreCase(":new"))
2926            ||(pColumn.getTableString().equalsIgnoreCase(":old"))){
2927            if (getAncestorStmt().tables != null){
2928                if (getAncestorStmt().tables.size() > 0){
2929                    getAncestorStmt().tables.getTable(0).getLinkedColumns().addObjectName(pColumn);
2930                    pColumn.setSourceTable(getAncestorStmt().tables.getTable(0));
2931                    ret = true;
2932                }
2933            }
2934        }
2935
2936        return ret;
2937    }
2938
2939    public boolean searchDaxVariableInStack(TObjectName pName){
2940        boolean ret = false;
2941        if (getVariableStack().size() == 0) return false;
2942        if (pName.getDbObjectType() == EDbObjectType.column) return false;
2943        for(int i=0;i<variableStack.size();i++){
2944            if (pName.toString().equalsIgnoreCase(((TObjectName) variableStack.get(i)).toString())){
2945                ret = true;
2946                break;
2947            }
2948        }
2949        return ret;
2950    }
2951
2952    boolean linkColumnToTableDax(TObjectName pColumn, ESqlClause pLocation){
2953        boolean lcResult = true ;
2954        TDaxFunction daxFunction = null;
2955        if (searchDaxVariableInStack(pColumn)) return false;
2956        if (getDaxFunctionStack().size() > 0){
2957            daxFunction = daxFunctionStack.peek();
2958        }
2959
2960        if (pColumn.getTableToken() != null){
2961            //TTable sourceTable = new TTable(new TObjectName(EDbObjectType.table,pColumn.getTableToken()));
2962            TTable sourceTable = new TTable(TObjectName.createObjectName (this.dbvendor,EDbObjectType.table,pColumn.getTableToken()));
2963            sourceTable.getLinkedColumns().addObjectName(pColumn);
2964            addToTables(sourceTable);
2965            if ((daxFunction != null) && (daxFunction.getDefaultTable() == null)){
2966                daxFunction.setDefaultTable(sourceTable);
2967            }
2968        }else{
2969            if ((daxFunction != null) &&(daxFunction.getDefaultTable() != null)){
2970                daxFunction.getDefaultTable().getLinkedColumns().addObjectName(pColumn);
2971            }else{
2972                ((TDaxStmt)this).getDefaultTable().getLinkedColumns().addObjectName(pColumn);
2973            }
2974        }
2975        return  lcResult;
2976    }
2977
2978    /**
2979     * 将列引用解析并绑定到其来源(表、子查询、CTE、表函数、OPENQUERY/UNNEST 等)。
2980     *
2981     * 功能概述:
2982     * 1) 针对 DAX 语法直接走 DAX 分支。
2983     * 2) 已绑定或标记“延迟到列解析器”的列直接返回。
2984     * 3) 设定列所在语法位置,并校验列名/保留字(含 MySQL true/false/default、内置函数等)。
2985     * 4) 处理厂商伪表/特殊前缀(Oracle :new/:old;SQL Server INSERTED/DELETED)。
2986     * 5) Insert All/VALUES 场景:优先在子查询结果集中/变量或过程参数中匹配。
2987     * 6) 在当前语句的 FROM 表集合中查找并建立绑定:
2988     *    - 限定列 table.col:按别名/表名匹配;对子查询/CTE/OPENQUERY 进一步在结果集中定位源列;
2989     *      命中后写入 linkedColumns,必要时将 TableToken 标记为 subquery_alias。
2990     *    - 非限定列 col:
2991     *      a. 先尝试同层 SELECT 列别名(支持 LATERAL 语义且位置在别名之后);
2992     *      b. 处理通配符“*”:收集所有来源表;
2993     *      c. 基础表通过元数据回调 fireOnMetaDatabaseTableColumn 校验;未命中则记录候选;
2994     *      d. 子查询/CTE/函数/UNNEST/PIVOT 分别按各自规则匹配。
2995     * 7) 命中后将列加入表的 linkedColumns 并设置 sourceTable/sourceColumn,必要时维持 isContinue 以继续匹配“*”。
2996     * 8) 若未命中:尝试变量/参数;再按条件(语句类型/位置/是否限定等)向上一层语句递归查找(维护 searchLevel)。
2997     * 9) 仍未命中:在顶层(searchLevel==0)按“候选唯一/或首表”兜底策略 {@link #linkToFirstTable(TObjectName, int)}。
2998     *
2999     * 参数:
3000     * @param pColumn   需要绑定的列名对象(方法会更新其 location、sourceTable、sourceColumn 等)
3001     * @param pLocation 列出现的语法位置(如 selectList、where、insertValues 等)
3002     *
3003     * 返回值:
3004     * @return 成功绑定到某个来源返回 true;未能绑定或被识别为变量/保留字等返回 false
3005     *
3006     * 厂商兼容:
3007     * - Oracle: 处理 :new/:old,Insert All 的 values 子句源自子查询的匹配
3008     * - SQL Server: 处理 INSERTED/DELETED 伪表
3009     * - MySQL: 对保留字/布尔字面量/内置函数名的特殊判断
3010     * - DAX: 委托 {@link #linkColumnToTableDax(TObjectName, ESqlClause)}
3011     *
3012     * 副作用:
3013     * - 更新 pColumn 的 location/searchLevel/sourceTable/sourceColumn/validate 状态
3014     * - 向命中的表写入 linkedColumns 或向别名列写入 targetColumns
3015     * - 对“*”列填充 sourceTableList;对子查询命中时可能将 TableToken 标为 subquery_alias
3016     * - 记录候选表数量并填充 pColumn.candidateTables,用于后续兜底
3017     *
3018     * 复杂度与顺序:
3019     * - 优先使用同层信息(别名/元数据/子查询结果),再逐层向外查找;避免无谓的上层搜索
3020     *
3021     * 注意:
3022     * - 本方法完成“旧算法”的快速联接,新的解析/消歧逻辑在解析器(如 TStmtScope/TAttributeResolver)中继续处理
3023     */
3024    /**
3025     * ClickHouse only: the expression-CTE ({@code WITH <expr> AS ident}) whose
3026     * alias matches the unqualified column reference, or null. The search
3027     * covers this statement's own WITH list and ascends ONLY through
3028     * set-operation membership (a compound query's WITH list is shared with
3029     * its members). It must NOT walk arbitrary lexical parents: ClickHouse
3030     * resolves closest-scope-first, so an outer alias may only win after this
3031     * scope's own tables failed — which the existing parent-walk recursion in
3032     * linkColumnToTable already provides (the recursion re-enters this check
3033     * at the parent's own level). A reference located inside a CTE's own
3034     * defining expression never matches that CTE (it is a column named like
3035     * the alias, not a self-reference).
3036     */
3037    private TCTE findExpressionCteInList(TCTEList lcCteList, TObjectName pColumn){
3038        if (lcCteList == null) return null;
3039        for(int i=0;i<lcCteList.size();i++){
3040            TCTE lcCte = lcCteList.getCTE(i);
3041            if ((lcCte.getExpression() == null) || (lcCte.getTableName() == null)) continue;
3042            if ((pColumn.getStartToken() != null)
3043                    && (lcCte.getExpression().getStartToken() != null)
3044                    && (lcCte.getExpression().getEndToken() != null)
3045                    && (pColumn.getStartToken().posinlist >= lcCte.getExpression().getStartToken().posinlist)
3046                    && (pColumn.getStartToken().posinlist <= lcCte.getExpression().getEndToken().posinlist)){
3047                continue;
3048            }
3049            if (SQLUtil.sameName(dbvendor, ESQLDataObjectType.dotColumn,
3050                    pColumn.toString(), lcCte.getTableName().toString())){
3051                return lcCte;
3052            }
3053        }
3054        return null;
3055    }
3056
3057    private TCTE findExpressionCteForAlias(TObjectName pColumn){
3058        TCustomSqlStatement stmt = this;
3059        while (stmt != null){
3060            TCTE found = findExpressionCteInList(stmt.getCteList(), pColumn);
3061            if (found != null) return found;
3062            if (!((stmt instanceof TSelectSqlStatement)
3063                    && ((TSelectSqlStatement)stmt).isChildOfCombinedQuery())) break;
3064            stmt = stmt.parentStmt;
3065        }
3066        return null;
3067    }
3068
3069    /**
3070     * ClickHouse only: like {@link #findExpressionCteForAlias} but across every
3071     * enclosing statement. Used ONLY where the local scope has already failed
3072     * to PROVE the column (e.g. an unverifiable table-function column) — a
3073     * proven outer alias then outranks the local guess.
3074     */
3075    private TCTE findExpressionCteForAliasInAnyScope(TObjectName pColumn){
3076        for(TCustomSqlStatement stmt = this; stmt != null; stmt = stmt.parentStmt){
3077            TCTE found = findExpressionCteInList(stmt.getCteList(), pColumn);
3078            if (found != null) return found;
3079        }
3080        return null;
3081    }
3082
3083    public boolean linkColumnToTable(TObjectName pColumn, ESqlClause pLocation){
3084        boolean lcResult = false,isContinue = false;
3085        int candidateTableCnt = 0;
3086        if (pColumn == null) return false;
3087        if (dbvendor == EDbVendor.dbvdax){
3088            return linkColumnToTableDax(pColumn,pLocation);
3089        }
3090
3091        // Skip alias definition columns - they define column names in alias clauses, not column references
3092        // Example: In "AS x (numbers, animals)", numbers and animals are column_alias type
3093        if (pColumn.getDbObjectType() == EDbObjectType.column_alias) {
3094            return true;
3095        }
3096
3097        if (pColumn.getSourceTable() != null) {
3098            lcResult = true;
3099            return lcResult;
3100        }
3101
3102        if (pColumn.getResolveStatus() == TBaseType.RESOLVE_DELAY_TO_COLUMN_RESOLVER) return true;
3103
3104        pColumn.setLocation(pLocation);
3105
3106        if (! pColumn.isValidColumnName(dbvendor)) {
3107            if (pColumn.isReservedKeyword()){
3108                if (
3109                        ((pColumn.getStartToken().tokencode != TBaseType.rrw_mysql_true)&&(!(pColumn.getStartToken().toString().equalsIgnoreCase("true"))))
3110                        &&((pColumn.getStartToken().tokencode != TBaseType.rrw_mysql_false)&&(!(pColumn.getStartToken().toString().equalsIgnoreCase("false"))))
3111                        &&(pColumn.getStartToken().tokencode != TBaseType.rrw_mysql_default)
3112                        &&(pColumn.getStartToken().tokencode != TBaseType.rrw_on)&&(!(pColumn.getStartToken().toString().equalsIgnoreCase("on")))
3113                   ) {
3114                        boolean mysqlBuiltFunction = false;
3115                        if (dbvendor == EDbVendor.dbvmysql){
3116                            mysqlBuiltFunction = functionChecker.isBuiltInFunction(pColumn.toString(),EDbVendor.dbvmysql,"6.0");
3117                        }
3118                        if (!mysqlBuiltFunction){
3119                            TSourceToken st1 = pColumn.getStartToken();
3120                            TSyntaxError err = new TSyntaxError(st1.toString()
3121                                    , st1.lineNo, st1.columnNo
3122                                    , String.format("Reserved keyword can't be column name")
3123                                    , EErrorType.sperror
3124                                    , TBaseType.MSG_ERROR_RESERVED_KEYWORD_CANT_USED_AS_COLUMN_NAME
3125                            ,this,st1.posinlist);
3126                            this.parseerrormessagehandle(err);
3127                        }
3128                }
3129            }
3130            return false;
3131        }
3132
3133        if (isOracleNewOldTable(pColumn)) return true;
3134        if (isSQLServerInsertedDelete(pColumn)) return true;
3135
3136        // oracle insert all statement,
3137        // WHEN id <= 3 THEN INTO dest_tab1 VALUES(id, description1)
3138        // column in values clause must be in the subquery of insert all statement
3139        if (pColumn.getLocation() == ESqlClause.insertValues){
3140            if (this instanceof TInsertSqlStatement){
3141                TInsertSqlStatement insertSqlStatement = (TInsertSqlStatement)this;
3142                // INSERT FIRST reads its subquery the same way; testing isInsertAll() alone bound
3143                // an INSERT FIRST VALUES name to the insert target instead
3144                if (insertSqlStatement.isMultiTableInsert() && insertSqlStatement.getSubQuery() != null){
3145                   // if (pColumn.getStartToken().tokencode == TBaseType.rrw_snowflake_default) return true;
3146                    lcResult = insertSqlStatement.getSubQuery().searchColumnInResultSet(pColumn, true);
3147                }
3148            }
3149
3150            if (lcResult) return true;
3151
3152            // value in values clause maybe parameter of the procedure/function parameter
3153            lcResult = locateVariableOrParameter(pColumn,true);
3154            if (lcResult) return true;
3155        }
3156
3157        // ClickHouse expression-CTE alias: WITH <expr> AS ident makes ident a
3158        // scalar symbol of the consuming query, not a column of its FROM
3159        // tables (default ClickHouse semantics: the alias shadows a
3160        // same-named table column). Binding it to a table would fabricate
3161        // lineage; resolve it to the CTE instead, before any table search.
3162        if ((dbvendor == EDbVendor.dbvclickhouse) && (!pColumn.isQualified())){
3163            TCTE lcExprCte = findExpressionCteForAlias(pColumn);
3164            if (lcExprCte != null){
3165                pColumn.setExpressionCteRef(lcExprCte);
3166                pColumn.setResolveStatus(TBaseType.RESOLVED_AND_FOUND);
3167                return false;
3168            }
3169        }
3170
3171        // SAP IQ and SQL Anywhere: a name this SELECT block provably cannot resolve is the alias of an enclosing
3172        // block (Mantis 4752). The rule is shared with resolver2: TSelectSqlStatement.findEnclosingAliasDefinition.
3173        if ((pColumn.searchLevel == 0) && (this instanceof TSelectSqlStatement) && (!pColumn.isQualified())){
3174            // getSqlEnv() needs a frame; without one no table's columns are known, which only keeps names columns
3175            TSQLEnv lcSqlEnv = ((getFrameStack() != null) && (!getFrameStack().isEmpty())) ? getSqlEnv() : null;
3176            TResultColumn lcOuterAlias = ((TSelectSqlStatement) this).findEnclosingAliasDefinition(pColumn, lcSqlEnv);
3177            if (lcOuterAlias != null){
3178                pColumn.setSourceColumn(lcOuterAlias);
3179                lcOuterAlias.getTargetColumns().addObjectName(pColumn);
3180                pColumn.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_COLUMN_ALIAS_IN_OLD_ALGORITHM);
3181                return true;
3182            }
3183        }
3184
3185        // MySQL reads a name declared as a local variable or a parameter of the routine as that variable, also when a
3186        // table of the statement has a column of that name ("Local Variable Scope and Resolution"); other dialects,
3187        // Oracle documented among them, read the column. The catalog knows more columns since Mantis 4797.
3188        if (isMySQLDialect() && (!pColumn.isQualified()) && (!pColumn.isColumnOnly())
3189                && locateVariableOrParameter(pColumn, true)){
3190            return false;
3191        }
3192
3193        boolean foundInMetaData = false;
3194
3195        for(int i=0;i<tables.size();i++){
3196            TTable lcTable = tables.getTable(i);
3197            if (lcTable.getEffectType() == ETableEffectType.tetSelectInto) continue;
3198            if (lcTable.getEffectType() == ETableEffectType.tetImplicitLateralDerivedTable) continue;
3199
3200            if (pColumn.isQualified()){
3201                lcResult = pColumn.resolveWithThisTable(lcTable);
3202                if ((lcResult) && (lcTable.getTableType() == ETableSource.subquery)){
3203                    pColumn.getTableToken().setDbObjectType(EDbObjectType.subquery_alias);
3204
3205                    int lcPos = lcTable.searchColumnInAlias(pColumn);
3206                    lcResult = lcPos>=0;
3207                    if (lcResult){
3208                        // 在 alias 中找到 source column, 还需要对应到 subquery select 中的 select list
3209                        // sql 见 https://e.gitee.com/gudusoft/projects/151613/tasks/list?issue=I8JR0W#note_23051633
3210                        if ((lcTable.getSubquery() != null)&&(lcTable.getSubquery().getResultColumnList() != null)){
3211                            pColumn.setSourceColumn(lcTable.getSubquery().getResultColumnList().getResultColumn(lcPos));
3212                        }
3213                    }else{
3214                        lcResult =  lcTable.getSubquery().searchColumnInResultSet(pColumn,true);
3215                    }
3216               //     lcTable.getSubquery().searchColumnInResultSet(pColumn,true);
3217
3218                }else if ((lcResult) && (lcTable.isCTEName())){
3219                    lcTable.getCTE().searchColumnInResultSet(this,lcTable,pColumn,true);
3220                }else if ((lcResult) && (lcTable.getTableType() == ETableSource.openquery)&&(lcTable.getSubquery() != null)){
3221                    pColumn.getTableToken().setDbObjectType(EDbObjectType.subquery_alias);
3222                    lcTable.getSubquery().searchColumnInResultSet(pColumn,true);
3223//                }else if ((lcResult) && (lcTable.getTableType() == ETableSource.unnest)&&(lcTable.getUnnestClause() != null)){
3224//                    pColumn.getTableToken().setDbObjectType(EDbObjectType.subquery_alias);
3225//                    lcTable.getSubquery().searchColumnInResultSet(pColumn,true);
3226                }
3227                if (lcResult&&pColumn.toString().endsWith("*")){
3228                    pColumn.getSourceTableList().add(lcTable);
3229//                    ArrayList<String> lcColumns = getColumnsInTable(lcTable);
3230//                    if (lcColumns != null){
3231//                        pColumn.getColumnsLinkedToStarColumn().addAll(lcColumns);
3232//                    }
3233                }
3234            }else {
3235              // column not qualified
3236
3237                // check if this is the column alias in current select list.
3238                // The binding rule itself lives in TResultColumnList.findLateralAliasDefinition()
3239                // so that this path and resolver2's handleSelectListAliasResolution() can never
3240                // disagree about what counts as an alias reference (MantisBT 4659).
3241                // In SAP IQ and SQL Anywhere an enclosing block's alias is taken only at searchLevel 0, by the rule
3242                // shared with resolver2 (findEnclosingAliasDefinition): searching upward here does not prove that
3243                // the blocks in between have no column of that name (Mantis 4752).
3244                if((!lcResult)&& ((!pColumn.isQualified()) && (this instanceof TSelectSqlStatement)&&(getResultColumnList() !=null))
3245                        && !((pColumn.searchLevel > 0) && TSQLEnv.isAliasOccludingColumn(dbvendor))){
3246                    TResultColumn lcField = getResultColumnList().findLateralAliasDefinition(dbvendor, pColumn);
3247                    if (lcField != null){
3248                        pColumn.setSourceColumn(lcField);
3249                        lcField.getTargetColumns().addObjectName(pColumn);
3250                        pColumn.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_COLUMN_ALIAS_IN_OLD_ALGORITHM);
3251                        return true;
3252                    }
3253                }
3254
3255                if (pColumn.getColumnNameOnly().equalsIgnoreCase("*")){
3256                    lcResult = true;
3257                    isContinue = true; // in order to match next table in the from clause
3258                    pColumn.getSourceTableList().add(lcTable);
3259//                    ArrayList<String> lcColumns = getColumnsInTable(lcTable);
3260//                    if (lcColumns != null){
3261//                        pColumn.getColumnsLinkedToStarColumn().addAll(lcColumns);
3262//                    }
3263                }else if (lcTable.isBaseTable()){
3264                    lcResult = fireOnMetaDatabaseTableColumn(
3265                                         lcTable.getPrefixServer()
3266                                        ,lcTable.getPrefixDatabase()
3267                                        ,lcTable.getPrefixSchema()
3268                                        ,lcTable.getName()
3269                                        ,pColumn.getColumnNameOnly());
3270                    if (! lcResult) {
3271                        candidateTableCnt++;
3272                        pColumn.getCandidateTables().addTable(lcTable);
3273                    }else{
3274                        List<TTable> lcOthers = otherTablesWithColumn(pColumn, i);
3275                        if (!lcOthers.isEmpty()){
3276                            // The catalog gives two tables of the statement this column: the name is ambiguous, and
3277                            // neither table is proven to be its own. The script's declarations made this reachable
3278                            // without caller metadata (Mantis 4797).
3279                            lcResult = false;
3280                            candidateTableCnt++;
3281                            pColumn.getCandidateTables().addTable(lcTable);
3282                            for (TTable lcOther : lcOthers){
3283                                candidateTableCnt++;
3284                                pColumn.getCandidateTables().addTable(lcOther);
3285                            }
3286                            break;
3287                        }
3288                        foundInMetaData = true;
3289                        isContinue = false;
3290                    }
3291
3292                }else if ((lcTable.getTableType() == ETableSource.subquery)
3293                            ||((lcTable.getTableType() == ETableSource.openquery)&&(lcTable.getSubquery() != null))){
3294
3295                    lcResult = lcTable.searchColumnInAlias(pColumn)>=0;
3296                    if (!lcResult){
3297                        lcResult = lcTable.getSubquery().searchColumnInResultSet(pColumn,(tables.size() == 1)
3298                                &&(pColumn.getCandidateTables().size() == 0));
3299                        if (! lcResult) {
3300                            candidateTableCnt++;
3301                            pColumn.getCandidateTables().addTable(lcTable);
3302                        }
3303                    }
3304
3305
3306//                    if (lcTable.isIncludeColumnAlias()){
3307//                       // System.out.println("subquery with alias:"+lcTable.getAliasClause().toString()+", skip search:"+pColumn.toString());
3308//
3309//                    }else{
3310//                        lcResult = lcTable.getSubquery().searchColumnInResultSet(pColumn,(tables.size() == 1)&&(pColumn.getCandidateTables().size() == 0));
3311//                        if (! lcResult) candidateTableCnt++;
3312//                    }
3313                }else  if (lcTable.isCTEName()){
3314                    lcResult = lcTable.getCTE().searchColumnInResultSet(this,lcTable,pColumn,tables.size() == 1);
3315                    if (! lcResult) {
3316                        candidateTableCnt++;
3317                        pColumn.getCandidateTables().addTable(lcTable);
3318                    }
3319                }else if (lcTable.getTableType() == ETableSource.function){
3320                    //  search in this table function
3321                        if(tables.size() == 1){
3322                                int lcInTableFunction = lcTable.getFuncCall().isColumnInThisTableFunction(this.getSqlEnv(),this.dbvendor,pColumn);
3323                                lcResult = ( lcInTableFunction != TBaseType.COLUMN_IN_TABEL_FUNCTION_NO);
3324                                // ClickHouse: an unverifiable table-function column
3325                                // (NOTSURE) must not outrank an expression-CTE alias
3326                                // visible in an enclosing scope — the alias is proven,
3327                                // the guessed column is not. Leaving it unmatched lets
3328                                // the parent-walk resolve the alias.
3329                                if (lcResult && (lcInTableFunction == TBaseType.COLUMN_IN_TABEL_FUNCTION_NOTSURE)
3330                                                && (dbvendor == EDbVendor.dbvclickhouse) && (!pColumn.isQualified())
3331                                                && (findExpressionCteForAliasInAnyScope(pColumn) != null)){
3332                                        lcResult = false;
3333                                }
3334                        }
3335                        else{
3336                                lcResult = ( lcTable.getFuncCall().isColumnInThisTableFunction(this.getSqlEnv(),this.dbvendor,pColumn)
3337                                                                        == TBaseType.COLUMN_IN_TABEL_FUNCTION_YES);
3338                        }
3339                }else if (lcTable.getTableType() == ETableSource.tableExpr 
3340                                && lcTable.getTableExpr().getExpressionType() == EExpressionType.function_t
3341                                && lcTable.getTableExpr().getFunctionCall() != null){
3342                    //  search in this table function
3343                    lcResult = ( lcTable.getTableExpr().getFunctionCall().isColumnInThisTableFunction(this.getSqlEnv(),this.dbvendor,pColumn)
3344                                                                        == TBaseType.COLUMN_IN_TABEL_FUNCTION_YES);
3345                }else if (lcTable.getTableType() == ETableSource.pivoted_table){
3346                    lcResult = fireOnMetaDatabaseTableColumn(
3347                            lcTable.getPrefixServer()
3348                            ,lcTable.getPrefixDatabase()
3349                            ,lcTable.getPrefixSchema()
3350                            ,lcTable.getName()
3351                            ,pColumn.getColumnNameOnly());
3352                    if (lcResult){
3353                            foundInMetaData = true;
3354                            isContinue = false;
3355                    }
3356                }else if (lcTable.getTableType() == ETableSource.unnest){
3357                    for(TObjectName objectName:lcTable.getLinkedColumns()){
3358                        if (SQLUtil.compareIdentifier(this.dbvendor, ESQLDataObjectType.dotColumn, objectName.toString(), pColumn.toString())){
3359                            lcResult = true;
3360                            break;
3361                        }
3362                    }
3363
3364                    if (!lcResult){
3365                        if (lcTable.getAliasClause() == null){
3366                            // this unnest() clause generate column with default name: "value"
3367                            if (pColumn.toString().equalsIgnoreCase("value")){
3368                                lcResult = true;
3369                            }
3370                        }else{
3371                        }
3372                    }
3373                    // A field of the STRUCTs the array holds, UNNEST([STRUCT(0 AS a, 1 AS b)]) (Mantis 4797).
3374                    if ((!lcResult) && TSelectSqlStatement.unnestHasStructField(lcTable, pColumn.getColumnNameOnly())){
3375                        lcResult = true;
3376                    }
3377                }//unnest
3378            }
3379
3380            if (lcResult) {
3381                lcTable.getLinkedColumns().addObjectName(pColumn);
3382                pColumn.setSourceTable(lcTable);
3383                // A real table binding in this scope outranks an alias mark a
3384                // wider-scope pre-pass may have left (ClickHouse closest-scope).
3385                pColumn.setExpressionCteRef(null);
3386               // pColumn.setValidate_column_status(TBaseType.COLUMN_LINKED_TO_TABLE_IN_OLD_ALGORITHM);
3387                if (!isContinue) break;
3388            }
3389        }
3390
3391        if ((lcResult) && (foundInMetaData)) return true;
3392
3393        // check variable after metadata checking
3394        if ((!pColumn.isColumnOnly()) && locateVariableOrParameter(pColumn)) return false;
3395
3396        // check if this is the column alias in current select list.
3397//        if((!lcResult)&& ((!pColumn.isPrefixed()) && (this instanceof TSelectSqlStatement)&&(getResultColumnList() !=null))){
3398//            for(int j=0;j<getResultColumnList().size();j++){
3399//                TResultColumn lcField = getResultColumnList().getResultColumn(j);
3400//                lcResult = lcField.isMatchedUsingAlias(pColumn);
3401//                if ((lcResult)&&(pColumn.getStartToken().posinlist > lcField.getAliasClause().getStartToken().posinlist)){
3402//                    pColumn.setSourceColumn(lcField);
3403//                    lcField.getTargetColumns().addObjectName(pColumn);
3404//                    break;
3405//                }else{
3406//                    lcResult = false;
3407//                }
3408//            }
3409//        }
3410
3411        if (lcResult) return true;
3412
3413        boolean isSearchUpLevel = (this.parentStmt != null);
3414
3415        if ((isSearchUpLevel) && (sqlstatementtype == ESqlStatementType.sstselect)){
3416            isSearchUpLevel = (pColumn.isQualified()
3417                                || (
3418//                                        (((TSelectSqlStatement)(this)).getLocation() != ESqlClause.elTable) &&
3419                                         (! ((TSelectSqlStatement)(this)).isQueryOfCTE())
3420                                    )
3421                               )
3422                            && (parentStmt.sqlstatementtype != ESqlStatementType.sstinsert)
3423                            && (!((pColumn.getLocation() == ESqlClause.selectList)&&(((TSelectSqlStatement)(this)).getLocation() == ESqlClause.join)))
3424                            && ((((TSelectSqlStatement)(this)).getLocation() != ESqlClause.pivot_in))
3425                            && (!((parentStmt.sqlstatementtype == ESqlStatementType.sstcreatetable)))
3426                            && (!((parentStmt.sqlstatementtype == ESqlStatementType.sstcreateview)))
3427//                            && (!((pColumn.getLocation() == ESqlClause.selectList)&&(parentStmt.sqlstatementtype == ESqlStatementType.sstcreatetable)))
3428//                            && (!((pColumn.getLocation() == ESqlClause.selectList)&&(parentStmt.sqlstatementtype == ESqlStatementType.sstcreateview)))
3429                            && (! ((pColumn.getLocation() == ESqlClause.selectList)
3430                                    &&(candidateTableCnt == 1) && (this instanceof TSelectSqlStatement)
3431                                    && (((TSelectSqlStatement)(this)).getLocation() == ESqlClause.elTable)
3432                                    ) ) // ref:mantis: #2628
3433                           // && ( ((TSelectSqlStatement)(this.parentStmt)).getSetOperatorType() == ESetOperatorType.none)
3434            ;
3435
3436            // An unqualified name is this block's own while one of its tables may have the column (Mantis 4797): a
3437            // derived table in FROM sees neither the FROM items next to it nor the enclosing block, and a subquery in
3438            // an expression reads its own tables first. A subquery's name compared with a column of its own tables is
3439            // the enclosing block's when its own tables do not prove it and the enclosing block does, by the rule
3440            // resolver2 applies too (TSelectSqlStatement.outerCorrelationTable): in (SELECT max(v) FROM
3441            // customfieldvalues WHERE resourceid = resumeid) under FROM (SELECT id AS resumeid FROM resumes) r,
3442            // resumeid is r's; in (select Jahr from m_version_table2 where Jahr234 = year(now())) both are
3443            // m_version_table2's.
3444            if (isSearchUpLevel && (!pColumn.isQualified())){
3445                TSQLEnv lcSqlEnv = ((getFrameStack() != null) && (!getFrameStack().isEmpty())) ? getSqlEnv() : null;
3446                TSelectSqlStatement lcSelect = (TSelectSqlStatement) this;
3447                isSearchUpLevel = !lcSelect.mayExposeColumn(pColumn.getColumnNameOnly(), lcSqlEnv)
3448                        || ((!isDerivedTableInFrom())
3449                            && (TSelectSqlStatement.outerCorrelationTable(pColumn, lcSelect, lcSqlEnv) != null))
3450                        // ClickHouse prefers an alias to a column (prefer_column_name_to_alias = 0): an enclosing
3451                        // expression-CTE alias of the name wins over a table here that may have the column.
3452                        || ((dbvendor == EDbVendor.dbvclickhouse) && (findExpressionCteForAliasInAnyScope(pColumn) != null));
3453            }
3454        }
3455
3456        if (isSearchUpLevel&&(pColumn.isContinueToSearch())){ // only search one level up, c:\prg\gsp_sqlfiles\TestCases\java\oracle\dbobject\berger_sqltest_04.sql
3457            boolean increaseLevel = true;
3458            if (parentStmt instanceof TSelectSqlStatement){
3459                if( ((TSelectSqlStatement)parentStmt).getSetOperatorType() != ESetOperatorType.none){
3460                   increaseLevel = false;
3461                }
3462            }
3463            if (increaseLevel){
3464                pColumn.searchLevel++;
3465            }
3466
3467            lcResult = parentStmt.linkColumnToTable(pColumn,pLocation);
3468
3469            if (increaseLevel){
3470                pColumn.searchLevel--;
3471            }
3472        }
3473
3474        if ((! lcResult) && (pColumn.searchLevel == 0)) {
3475            if (this.sqlstatementtype == ESqlStatementType.sstselect){
3476                if( ((TSelectSqlStatement)this).getSetOperatorType() == ESetOperatorType.none){
3477                    //                    USING _spVV0 (INTEGER)
3478                    //                            INSERT INTO table3
3479                    //                    SELECT :_spVV0,x. *,m.col3
3480                    //                    from ((           select table1.col1, (table1.col1 + table5.col2) c from table1
3481                    //                            union all select col3,col4 from table2) x
3482                    //                    cross join (select id from table2) m )
3483
3484                    // table5 in the above sql only link to the nearest level sql, but not to up-level which is union all
3485
3486                    linkToFirstTable(pColumn,candidateTableCnt);
3487                }
3488            }else{
3489                linkToFirstTable(pColumn,candidateTableCnt);
3490            }
3491        }
3492
3493        return lcResult;
3494    }
3495
3496
3497    /**
3498     *
3499     * @deprecated As of v1.6.0.1, use  {@link #linkColumnToTable} instead
3500     */
3501    /**
3502     * True when {@code cr} is an Oracle pseudocolumn rather than a reference to
3503     * a column of a table, judged only on what the statement itself proves.
3504     *
3505     * <p>The name list lives in {@link OraclePseudoColumnUtil}; this method
3506     * supplies the statement context that decides the non-reserved names.
3507     * Nothing here consults a catalog, so names that are indistinguishable from
3508     * a real column without one - {@code ORA_ROWSCN}, {@code OBJECT_ID},
3509     * {@code OBJECT_VALUE}, {@code XMLDATA}, {@code COLUMN_VALUE} - are
3510     * deliberately not claimed. {@code OBJECT_ID} in particular is a real
3511     * column of {@code ALL_OBJECTS}, and claiming it would silently drop that
3512     * lineage.</p>
3513     *
3514     * @param cr the column reference being linked
3515     * @return true if {@code cr} is provably a pseudocolumn
3516     */
3517    protected boolean isOraclePseudoColumnReference(TObjectName cr){
3518        if (dbvendor != EDbVendor.dbvoracle) return false;
3519        if (cr == null) return false;
3520        // getColumnNameOnly() is the raw text of the LAST segment, quotes
3521        // included, so a quoted "ROWID" simply fails to match the name list -
3522        // which is the correct outcome, since "ROWID" and ROWID are different
3523        // names in Oracle. Do NOT add a getQuoteType() guard here: that method
3524        // reports the quote state of the FIRST segment, so it would reject
3525        // "seq".NEXTVAL, whose terminal segment is unquoted and really is the
3526        // sequence pseudocolumn.
3527        String nameOnly = cr.getColumnNameOnly();
3528        if (nameOnly == null || nameOnly.length() == 0) return false;
3529
3530        // Reserved words with no owning table (ROWNUM, LEVEL). Oracle rejects
3531        // an unquoted column of these names, so no qualifier or clause can make
3532        // this a real column reference. ROWID is deliberately NOT here: it is
3533        // scoped to one table, keeps its attribution, and is retyped in
3534        // TObjectName.setSourceTable() instead.
3535        if (OraclePseudoColumnUtil.isDetachedReservedPseudoColumn(nameOnly)) return true;
3536
3537        // CONNECT_BY_ISLEAF / CONNECT_BY_ISCYCLE are not reserved. They are
3538        // pseudocolumns only inside a hierarchical query, and only unqualified:
3539        // Oracle has no table to qualify them with, so t.CONNECT_BY_ISLEAF is a
3540        // reference to a column somebody created called CONNECT_BY_ISLEAF.
3541        if (OraclePseudoColumnUtil.isConnectByPseudoColumn(nameOnly)
3542                && cr.getTableToken() == null
3543                && hasHierarchicalQueryClause()) return true;
3544
3545        // VERSIONS_* are only pseudocolumns when the table they come from
3546        // carries a flashback VERSIONS clause.
3547        if (OraclePseudoColumnUtil.isVersionsPseudoColumn(nameOnly)
3548                && hasFlashbackVersionsClause(cr.getTableString())) return true;
3549
3550        return false;
3551    }
3552
3553    /**
3554     * True when {@code cr} is {@code [schema.]sequence.NEXTVAL} or
3555     * {@code [schema.]sequence.CURRVAL}.
3556     *
3557     * <p>Neither name is reserved, so this needs the qualifier to be something
3558     * other than a table or alias in scope: {@code x.CURRVAL} where {@code x}
3559     * is a table alias is an ordinary reference to a column named
3560     * {@code CURRVAL}, and an unqualified {@code CURRVAL} likewise.</p>
3561     *
3562     * <p>Kept separate from {@link #isOraclePseudoColumnReference} because the
3563     * two get different treatment - see the call site.</p>
3564     *
3565     * @param cr the column reference being linked
3566     * @return true if {@code cr} is a sequence pseudocolumn
3567     */
3568    protected boolean isOracleSequencePseudoColumnReference(TObjectName cr){
3569        if (dbvendor != EDbVendor.dbvoracle) return false;
3570        if (cr == null) return false;
3571        // See isOraclePseudoColumnReference(): quote state is segment-specific
3572        // and already carried by getColumnNameOnly(); getQuoteType() reads the
3573        // wrong segment for a qualified name.
3574        String nameOnly = cr.getColumnNameOnly();
3575        if (nameOnly == null || nameOnly.length() == 0) return false;
3576        return OraclePseudoColumnUtil.isSequencePseudoColumn(nameOnly)
3577                && cr.getTableToken() != null
3578                && !isTableOrAliasInScope(cr.getTableString());
3579    }
3580
3581    /**
3582     * True when this statement (or the statement it belongs to) carries an
3583     * Oracle {@code CONNECT BY}.
3584     */
3585    private boolean hasHierarchicalQueryClause(){
3586        // Deliberately NOT walking up to the parent statement. CONNECT BY
3587        // pseudocolumns belong to the query that carries the clause; they are
3588        // not visible to a subquery nested inside it. Walking up would claim
3589        // CONNECT_BY_ISLEAF in
3590        //   SELECT (SELECT CONNECT_BY_ISLEAF FROM other) FROM t CONNECT BY ...
3591        // where it is an ordinary column of "other", and drop that edge.
3592        if (this instanceof TSelectSqlStatement){
3593            return ((TSelectSqlStatement)this).getHierarchicalClause() != null;
3594        }
3595        return false;
3596    }
3597
3598    /**
3599     * True when any table in scope carries a flashback {@code VERSIONS BETWEEN}
3600     * clause, which is what makes the {@code VERSIONS_*} pseudocolumns
3601     * available.
3602     */
3603    private boolean hasFlashbackVersionsClause(String qualifier){
3604        // Same scoping rule as hasHierarchicalQueryClause(): the VERSIONS_*
3605        // pseudocolumns come from a table in THIS statement's FROM clause, so
3606        // no parent-chain walk. An inner subquery of a version query does not
3607        // inherit them.
3608        if (tables == null) return false;
3609        // When the reference names a table, only THAT table's clause counts.
3610        // In "FROM x VERSIONS BETWEEN ... , y", y.VERSIONS_XID is an ordinary
3611        // column of y - y is not being version-queried.
3612        if (qualifier != null && qualifier.length() > 0){
3613            int idx = tables.searchTableByNameOrAlias(qualifier);
3614            if (idx < 0) return false;
3615            TFlashback flashback = tables.getTable(idx).getFlashback();
3616            return flashback != null && flashback.isVersionsQuery();
3617        }
3618        for (int i = 0; i < tables.size(); i++){
3619            TFlashback flashback = tables.getTable(i).getFlashback();
3620            if (flashback != null && flashback.isVersionsQuery()) return true;
3621        }
3622        return false;
3623    }
3624
3625    /**
3626     * True when {@code name} matches a table name or alias visible to this
3627     * statement. Used to tell {@code sequence.CURRVAL} from
3628     * {@code tablealias.CURRVAL}.
3629     */
3630    private boolean isTableOrAliasInScope(String name){
3631        if (name == null || name.length() == 0) return false;
3632        TCustomSqlStatement stmt = this;
3633        while (stmt != null){
3634            if (stmt.tables != null && stmt.tables.searchTableByNameOrAlias(name) >= 0) return true;
3635            stmt = stmt.getParentStmt();
3636        }
3637        return false;
3638    }
3639
3640    public void linkColumnReferenceToTable(TObjectName cr, ESqlClause plocation){
3641        // this is the column name, link it to table
3642        if (cr == null) return;
3643        cr.setLocation(plocation);
3644        if (cr.getObjectType() == TObjectName.ttobjVariable) return;
3645        if (cr.getObjectType() == TObjectName.ttobjColumnAlias) return;
3646        if ((this.dbvendor == EDbVendor.dbvsybase)||(this.dbvendor == EDbVendor.dbvsybasease || this.dbvendor == EDbVendor.dbvsqlanywhere || this.dbvendor == EDbVendor.dbvsybaseiq)){
3647            TSourceToken pt = cr.getPartToken();
3648            if ( pt != null){
3649                if (pt.tokentype == ETokenType.ttdqstring){
3650                //"0123", quoted string start with a number can't a column
3651                    if ((pt.toString().charAt(1) >= '0')
3652                        &&(pt.toString().charAt(1) <= '9')){
3653                        return;
3654                    }else if (pt.toString().length() == 2){
3655                        //"", empty
3656                        return;
3657                    }else if (pt.toString().substring(1,pt.toString().length()-1).trim().length() == 0){
3658                        //"  "
3659                        return;
3660                    }
3661                }
3662            }
3663        }
3664
3665
3666        if (cr.getPartToken() != null){
3667            if (cr.getPartToken().tokentype == ETokenType.ttkeyword){
3668                boolean reservedKeyword = false;
3669                switch (dbvendor){
3670                    case dbvmssql:
3671                        //reservedKeyword = ! this.getGsqlparser().getFlexer().canBeColumnName(cr.getPartToken().tokencode);
3672                        reservedKeyword = ! TLexerMssql.canBeColumnName(cr.getPartToken().tokencode);
3673                        break;
3674                    case dbvsybase:
3675                        reservedKeyword = keywordChecker.isKeyword(cr.getPartToken().toString(), EDbVendor.dbvsybase, "15.7", true);
3676                        break;
3677                    case dbvsybasease:
3678                        // Shares dbvsybase's keyword DATA file (it is the ASE
3679                        // reserved-word list); independent case block per HC-5.
3680                        reservedKeyword = keywordChecker.isKeyword(cr.getPartToken().toString(), EDbVendor.dbvsybase, "15.7", true);
3681                        break;
3682                    case dbvsqlanywhere:
3683                        // Same shared keyword DATA file; independent case per HC-5.
3684                        reservedKeyword = keywordChecker.isKeyword(cr.getPartToken().toString(), EDbVendor.dbvsybase, "15.7", true);
3685                        break;
3686                    case dbvsybaseiq:
3687                        // Same again; independent case per HC-5.
3688                        reservedKeyword = keywordChecker.isKeyword(cr.getPartToken().toString(), EDbVendor.dbvsybase, "15.7", true);
3689                        break;
3690                    default:
3691                        break;
3692                }
3693                if (reservedKeyword) return;
3694            }
3695        }
3696
3697        // let's check is this columnreference is variable or parameter of plsql function/procedure
3698      //  if (locateVariableOrParameter(cr)) return;
3699
3700//        if ((cr.getPartToken() != null)&&((dbvendor == EDbVendor.dbvmssql)||(dbvendor == EDbVendor.dbvsybase))){
3701//            if ((cr.getPartToken().tokentype == ETokenType.ttkeyword)&&(!(this.getGsqlparser().getFlexer().canBeColumnName(cr.getPartToken().tokencode)))){
3702//                // keyword can't be column name:
3703//                //select * From dbo.table Where DATEDIFF(day, create_date, expiry_date) < 14
3704//               return;
3705//            }
3706//        }
3707
3708        if ((cr.toString().startsWith("@")))
3709//            if ((cr.toString().endsWith("*"))||(cr.toString().startsWith("@")))
3710        {
3711            cr.setObjectType(TObjectName.ttobjNotAObject);
3712            return;
3713        }
3714
3715        if (dbvendor == EDbVendor.dbvoracle){
3716            if ( //(cr.toString().compareToIgnoreCase ("rowid") == 0)||
3717                    (cr.toString().compareToIgnoreCase ("sysdate") == 0)
3718            || (cr.toString().compareToIgnoreCase ("nextval") == 0)
3719            || (cr.toString().compareToIgnoreCase ("rownum") == 0)
3720            || (cr.toString().compareToIgnoreCase ("level") == 0)
3721                    ){
3722                cr.setObjectType(TObjectName.ttobjNotAObject);
3723                if (cr.getDbObjectType() == EDbObjectType.unknown){
3724                    cr.setDbObjectType(EDbObjectType.notAColumn);
3725                }
3726                return;
3727            }
3728            // The comparisons above are against the whole rendered text, so they
3729            // only ever fire for a bare, unqualified name. Oracle documents 18
3730            // pseudocolumns and most of them reach here qualified (X.ROWID,
3731            // SEQ.CURRVAL) or under a spelling the list never had. Left alone
3732            // they fall through to checkColumnReferenceInTables() below and get
3733            // bound to a table in the FROM clause, which asserts that e.g.
3734            // ORA_ROWSCN is a column of that table (Mantis #4675).
3735            if (isOracleSequencePseudoColumnReference(cr)){
3736                // [schema.]sequence.NEXTVAL|CURRVAL. Detach it from the FROM
3737                // clause but leave dbObjectType alone, which keeps CURRVAL on
3738                // the exact footing NEXTVAL has always had (unknown, no source
3739                // table). Promoting these to notAColumn would reclassify them
3740                // from column references to constants in DataFlowAnalyzer and
3741                // move existing lineage output; the defect reported was CURRVAL
3742                // being bound to an unrelated table, not its type.
3743                // Deliberately no setSourceTable(null) here: nothing has linked
3744                // this reference yet, and that setter has a side effect of
3745                // forcing dbObjectType to column, which is exactly the type we
3746                // are trying to leave alone.
3747                cr.setObjectType(TObjectName.ttobjNotAObject);
3748                cr.setValidate_column_status(TBaseType.VALIDATED_CAN_NOT_BE_A_COLUMN_NAME);
3749                return;
3750            }
3751            if (isOraclePseudoColumnReference(cr)){
3752                cr.setObjectType(TObjectName.ttobjNotAObject);
3753                cr.setSourceTable(null);
3754                cr.setValidate_column_status(TBaseType.MARKED_NOT_A_COLUMN_IN_COLUMN_RESOLVER);
3755                cr.setDbObjectTypeDirectly(EDbObjectType.notAColumn);
3756                return;
3757            }
3758        }
3759
3760        if (((cr.toString().toUpperCase(java.util.Locale.ROOT).startsWith("INSERTED"))||(cr.toString().toUpperCase(java.util.Locale.ROOT).startsWith("DELETED")))&&(plocation == ESqlClause.output)&&(targetTable != null)){
3761            targetTable.getObjectNameReferences().addObjectName(cr);
3762            return;
3763        }
3764
3765        if ( ((cr.toString().toUpperCase(java.util.Locale.ROOT).startsWith(":NEW"))
3766              ||(cr.toString().toUpperCase(java.util.Locale.ROOT).startsWith(":OLD")))
3767             &&(this.getTopStatement() instanceof TPlsqlCreateTrigger)
3768             &&(dbvendor == EDbVendor.dbvoracle)){
3769             this.getTopStatement().tables.getTable(0).getObjectNameReferences().addObjectName(cr);
3770            return;
3771        }
3772
3773
3774
3775        int ret = this.tables.checkColumnReferenceInTables(cr);
3776         if (ret >= 0) {
3777             TTable lcTable = this.tables.getTable(ret);
3778             if (lcTable.isBaseTable()){
3779                lcTable.getObjectNameReferences().addObjectName(cr);
3780             }else if (lcTable.isCTEName()){
3781                //WITH temp
3782                //     AS (SELECT *
3783                //         FROM   sysibm.systables),
3784                //     temp1
3785                //     AS (SELECT *
3786                //         FROM   sysibm.syscolumns)
3787                //SELECT *
3788                //FROM   temp A
3789                //       INNER JOIN temp1 B
3790                //               ON A.creator = B.tbcreator
3791                //                  AND A.name = B.tbname
3792                 TCTE lccte = findCTEByName(lcTable.toString());
3793                 if (lccte != null){
3794                     TObjectName objectName = new TObjectName();
3795                     objectName.init(cr.getPartToken());
3796                     if (lccte.getSubquery() != null){
3797                         lccte.getSubquery().linkColumnReferenceToTable(objectName,plocation);
3798                     }
3799                 }
3800             }else if (lcTable.getTableType() == ETableSource.subquery){
3801                // link s2t1a1 to  subselect2table1 via s2
3802                //select
3803                //       s2.s2t1a1
3804                //from
3805                //    (
3806                //       select s2t1.*
3807                //          from subselect2table1 s2t1
3808                //    ) s2
3809               TSelectSqlStatement subquery = lcTable.getSubquery();
3810
3811                 if(((subquery.getValueClause() == null))&&(!subquery.isCombinedQuery())&&(subquery.getResultColumnList() != null)&&(subquery.getResultColumnList().size() == 1)){
3812                     TResultColumn lcColumn = subquery.getResultColumnList().getResultColumn(0);
3813                     if (lcColumn.toString().endsWith("*")){
3814                        boolean isfound = false;
3815
3816                        for(int i=0;i<subquery.tables.size();i++){
3817                            if (subquery.tables.getTable(i).getTableType() == ETableSource.subquery) continue;
3818                            String columnStr = null;
3819                            if (cr.getPartToken() != null){
3820                                //cr.getObjectType() is not ttObjColumn, so we can't use
3821                                // getColumnToken, this is a bug, need to check it later.
3822                                columnStr = cr.getPartToken().toString();
3823                            }
3824                            if (this.fireOnMetaDatabaseTableColumn(
3825                                    subquery.tables.getTable(i).getTableName().getServerString(),
3826                                    subquery.tables.getTable(i).getTableName().getDatabaseString(),
3827                                    subquery.tables.getTable(i).getTableName().getSchemaString(),
3828                                    subquery.tables.getTable(i).getName(),columnStr)){
3829                                subquery.tables.getTable(i).getObjectNameReferences().addObjectName(cr);
3830                                isfound = true;
3831                                break;
3832                            }
3833                        }
3834
3835
3836
3837                         if (!isfound)
3838                         {
3839                             if(subquery.tables.size() > 1){
3840                                 cr.setTableDetermined(false);
3841                             }
3842                           for(int i=0;i<subquery.tables.size();i++){
3843                             subquery.tables.getTable(i).getObjectNameReferences().addObjectName(cr);
3844                           }
3845                         }
3846
3847                     } // "*"
3848                 }
3849             }
3850         }else if (ret == -2){
3851           // no qualifier before column, check is this column of a cte, if not,set it to non-cte table
3852           boolean isfound = false;
3853             for (int i=0;i<this.tables.size();i++){
3854                 if ((this.tables.getTable(i).isCTEName()) &&(this.tables.getTable(i).getCteColomnReferences() != null)){
3855                     if (this.tables.getTable(i).getCteColomnReferences().searchColumnReference(cr) >= 0){
3856                        this.tables.getTable(i).getObjectNameReferences().addObjectName(cr);
3857                         isfound = true;
3858                         break;
3859                     }
3860                 }
3861             }
3862
3863           // no qualifier before column, but we still need to check uplevel table like this:
3864            //SELECT
3865            //       col1 ,
3866            //
3867            //           (    SELECT col2
3868            //                FROM tab1
3869            //                WHERE col2 = col1      )
3870            //   FROM tab2
3871           // we need to link col1 to tab2 in up level, but not to tab1
3872            if ((!isfound) &&(
3873                            (cr.getLocation() != ESqlClause.resultColumn)
3874                          &&(cr.getLocation() != ESqlClause.insertColumn)
3875                                    &&(cr.getLocation() != ESqlClause.mergeInsert)
3876                                    &&(cr.getLocation() != ESqlClause.selectList)
3877            ) ){  // code #111
3878                TCustomSqlStatement lcParent = null;
3879                lcParent = this.getParentStmt();
3880               while ( lcParent != null) {
3881                   TTable lcTable;
3882                 //ret = lcParent.tables.checkColumnReferenceInTables(cr);
3883                   if (lcParent.sqlstatementtype != ESqlStatementType.sstselect) {
3884                       break;
3885                   }
3886                   for (int i=0;i<lcParent.tables.size();i++){
3887                       lcTable = lcParent.tables.getTable(i);
3888                       if (lcTable.getTableType() == ETableSource.objectname) {
3889                           for(int k = 0; k< lcTable.getObjectNameReferences().size();k++){
3890                               if (lcTable.getObjectNameReferences().getObjectName(k).isTableDetermined()){
3891                                   if (SQLUtil.compareIdentifier(this.dbvendor, ESQLDataObjectType.dotColumn, cr.toString(), lcTable.getObjectNameReferences().getObjectName(k).toString())){
3892                                      isfound = true;
3893                                       break;
3894                                   }
3895                               }
3896                           }
3897                       if (isfound) break;
3898                       }
3899                   }
3900
3901                 if (isfound){
3902                     break;
3903                 }else{
3904                     lcParent = lcParent.getParentStmt();
3905                 }
3906               } // while
3907
3908             } // end of code #111
3909
3910            if (!isfound){
3911                isfound = checkNonQualifiedColumnReferenceInSubQueryOfUplevelStmt(cr
3912                        , ((plocation == ESqlClause.resultColumn)
3913                            ||(plocation == ESqlClause.insertColumn)
3914                                ||(plocation == ESqlClause.mergeInsert)
3915                                ||(plocation == ESqlClause.selectList)
3916                        )
3917                );
3918            }
3919
3920             if ((!isfound)&&(this.tables.size() > 0)){
3921                 int candidate = 0, firstCandidate = -1;
3922                 // add this column reference to first non-cte( or cte with column list is null) and non-subquery table
3923                 for (int i=0;i<this.tables.size();i++){
3924                     // no qualified column can't belong to a table with alias, that column must be qualified if it's belong to a table with alias
3925                     //if (this.tables.getTable(i).aliasClause != null) continue;
3926                     if (
3927                             (
3928                                     (!this.tables.getTable(i).isCTEName())
3929                                ||((this.tables.getTable(i).isCTEName())&&(this.tables.getTable(i).getCteColomnReferences() == null))
3930                             )&&((this.tables.getTable(i).getTableType() != ETableSource.subquery))
3931                     )
3932                     {
3933                         candidate++;
3934                         if (firstCandidate == -1) firstCandidate = i;
3935                         if (this.fireOnMetaDatabaseTableColumn(
3936                                    this.tables.getTable(i).getTableName().getServerString(),
3937                                    this.tables.getTable(i).getTableName().getDatabaseString(),
3938                                    this.tables.getTable(i).getTableName().getSchemaString(),
3939                                    this.tables.getTable(i).getName(),cr.toString())){
3940                                this.tables.getTable(i).getObjectNameReferences().addObjectName(cr);
3941                                isfound = true;
3942                                break;
3943                            }
3944                         else{
3945                             this.tables.getTable(i).getObjectNameReferences().addObjectName(cr);
3946                             if (this.tables.size() > 1){
3947                                 cr.setTableDetermined(false);
3948                             }
3949                             isfound = true;
3950                             break;
3951                         }
3952                     }
3953                  }
3954                 if ((!isfound) && (candidate == 1)){
3955                     this.tables.getTable(firstCandidate).getObjectNameReferences().addObjectName(cr);
3956                 }
3957             }
3958         }else if (ret == -1){
3959             TCustomSqlStatement lcParent = null;
3960             lcParent = this.getParentStmt();
3961            while ( lcParent != null) {
3962              ret = lcParent.tables.checkColumnReferenceInTables(cr);
3963              if (ret >= 0){
3964               lcParent.tables.getTable(ret).getObjectNameReferences().addObjectName(cr);
3965               break;
3966              }else{
3967                  lcParent = lcParent.getParentStmt();
3968              }
3969            } // while
3970         } //-1
3971
3972    }
3973
3974    /**
3975     * Found out is a non qualified column is a column in uplevel subquery table like this:
3976     * take ma_parkey for example: ma_parkey is not a physical column
3977     * 
3978        SELECT c_mandant
3979             , CASE WHEN EXISTS (SELECT 1
3980                                   FROM CDS_H_GRUPPE  GRP1
3981                                  WHERE GRP1.c_mandant = c_mandant
3982                                    AND GRP1.parkey1       = ma_parkey)
3983                      THEN 1
3984                  ELSE NULL
3985               END MA_ME
3986          FROM (SELECT c_mandant
3987                     , CASE WHEN funktionscode = 'U'
3988                              THEN parkey1
3989                          ELSE parkey2
3990                       END MA_PARKEY
3991                  FROM
3992                       CDS_H_GRUPPE
3993               )
3994     */
3995    public boolean checkNonQualifiedColumnReferenceInSubQueryOfUplevelStmt(TObjectName crf,boolean sameLevelOnly){
3996        boolean ret = false;
3997
3998        TCustomSqlStatement lcParent = null;
3999        lcParent = this;//getParentStmt();
4000       while ( lcParent != null) {
4001           TTable lcTable;
4002             for (int i=0;i<lcParent.tables.size();i++){
4003                 lcTable = lcParent.tables.getTable(i);
4004
4005                 if ((lcTable.getTableType() != ETableSource.subquery)) {continue;}
4006
4007                 ret = isColumnNameInSelectList(crf.toString(),lcTable.subquery);
4008                if (ret) {break;}
4009
4010             }
4011          if (ret) {break;}
4012           else{
4013              if (sameLevelOnly){
4014                  lcParent = null;
4015              }else{
4016                lcParent = lcParent.getParentStmt();
4017              }
4018          }
4019       } // while
4020
4021        return ret;
4022    }
4023
4024    private boolean isColumnNameInSelectList(String pColumn, TSelectSqlStatement pSelect){
4025        // Iterative rewrite of the original recursive descent. Provably identical
4026        // output: it visits exactly the same set of leaf (non-combined) SELECTs as
4027        // the recursion, in the same left-before-right order, and applies the
4028        // identical per-column test (alias match, else expr match, case-insensitive).
4029        // The only reason for the change is that UNION trees are left-leaning and
4030        // can be thousands of branches deep, so recursive descent risks
4031        // StackOverflowError (see repo CLAUDE.md: iterative UNION traversal).
4032        Deque<TSelectSqlStatement> pending = new ArrayDeque<>();
4033        pending.push(pSelect);
4034        while (!pending.isEmpty()) {
4035            TSelectSqlStatement select = pending.pop();
4036            if (select == null) {
4037                continue;
4038            }
4039            if (select.isCombinedQuery()) {
4040                // Push right then left so left is popped/processed first, matching
4041                // the recursion's "check left before right" order.
4042                pending.push(select.getRightStmt());
4043                pending.push(select.getLeftStmt());
4044                continue;
4045            }
4046            if (select.getResultColumnList() != null){ //if it's a db2 value row, then getResultColumnList() will be null
4047                TResultColumnList resultColumns = select.getResultColumnList();
4048                for(int j=0;j<resultColumns.size();j++){
4049                   TResultColumn lcColumn = resultColumns.getResultColumn(j);
4050                   boolean ret = false;
4051                   if (lcColumn.getAliasClause() != null){
4052                       ret = SQLUtil.compareIdentifier(this.dbvendor, ESQLDataObjectType.dotColumn, pColumn, lcColumn.getAliasClause().toString());
4053                   }
4054                   if (ret) return true;
4055                   ret = pColumn.equalsIgnoreCase(lcColumn.getExpr().toString());
4056                   if (ret) return true;
4057                }
4058            }
4059        }
4060        return false;
4061    }
4062
4063    public TCustomSqlStatement getTopStatement(){
4064        TCustomSqlStatement ret = this;
4065        while (ret.getParentStmt() != null){
4066            ret = ret.getParentStmt();
4067        }
4068        return ret;
4069    }
4070
4071
4072//    public String toScript(){
4073//        if (!isChanged()){
4074//            return this.toString();
4075//        }
4076//        return super.toScript();
4077//    }
4078
4079}
4080
4081class constantVisitor extends TParseTreeVisitor {
4082    private boolean inWhere = false,inExprList = false;
4083    public void preVisit(TWhereClause node){
4084        inWhere = true;
4085    }
4086
4087    public void postVisit(TWhereClause node){
4088        inWhere = false;
4089    }
4090
4091    public void preVisit(TExpression node){
4092        if (inWhere){
4093            switch (node.getExpressionType()){
4094                case list_t:
4095                    inExprList = true;
4096                    boolean isNumber = true;
4097                    if (node.getExprList().size() > 0){
4098                        // check the type of the constant in the expr list
4099                        TExpression expr = node.getExprList().getExpression(0);
4100                        if (expr.getExpressionType() == EExpressionType.simple_constant_t){
4101                            if (expr.getConstantOperand().getLiteralType() == ELiteralType.etString){
4102                                isNumber = false;
4103                            }
4104                        }
4105                    }
4106
4107                    TSourceToken lcStartToken = node.getStartToken();
4108                    TSourceToken lcEndToken = node.getEndToken();
4109                    int tokenPos = 0;
4110                    if ((lcEndToken != null) && (lcStartToken != null)){
4111                        TSourceToken lcCurrentToken = lcStartToken;
4112                        while (lcCurrentToken != null){
4113
4114                            if (lcCurrentToken.equals(lcEndToken)){
4115                                break;
4116                            }else{
4117
4118                                if (tokenPos == 1){
4119                                    if (isNumber){
4120                                        lcCurrentToken.setTextWithBackup("999");
4121                                    }else{
4122                                        lcCurrentToken.setTextWithBackup("'placeholder_str'");
4123                                    }
4124                                }else if (tokenPos > 1){
4125                                    lcCurrentToken.tokenstatus = ETokenStatus.tsdeleted;
4126                                }
4127
4128                                lcCurrentToken = lcCurrentToken.getNextTokenInChain();
4129                                tokenPos++;
4130                            }
4131                        }
4132                    }
4133
4134                    break;
4135            }
4136        } // where
4137    }
4138
4139    public void postVisit(TExpression node){
4140        if (inWhere){
4141            switch (node.getExpressionType()){
4142                case list_t:
4143                    inExprList = false;
4144                    break;
4145            }
4146        }
4147    }
4148
4149    public void preVisit(TConstant node){
4150        if (inWhere&&(!inExprList)){
4151            switch (node.getLiteralType()){
4152                case etNumber:
4153                case etFloat:
4154                    node.getStartToken().setTextWithBackup("999");
4155                    break;
4156                case etString:
4157                    node.getStartToken().setTextWithBackup("'placeholder_str'");
4158                    break;
4159            }
4160        }
4161    }
4162
4163    public void preVisit(TFunctionCall node){
4164        if (TBaseType.as_canonical_f_decrypt_replace_password){
4165            int i = TBaseType.searchCryptFunction(node.getFunctionName().toString());
4166
4167            if (i>0){ // find this function
4168                if (node.getArgs().size() >= i){
4169                    TExpression secondArg = node.getArgs().getExpression(i-1);
4170                    if (secondArg.getExpressionType() == EExpressionType.simple_constant_t){
4171                        TConstant constant = secondArg.getConstantOperand();
4172                        constant.getValueToken().setTextWithBackup("'***'");
4173                        //System.out.println(node.toString()+":"+constant.toString());
4174                    }else if (secondArg.getExpressionType() == EExpressionType.simple_object_name_t){
4175                        TObjectName objectName = secondArg.getObjectOperand();
4176                        objectName.getStartToken().setTextWithBackup("'***'");
4177                        //System.out.println(node.toString()+":"+constant.toString());
4178                    }
4179                }
4180            }
4181
4182        }
4183    }
4184
4185    void processConstant(TConstant node){
4186        switch (node.getLiteralType()){
4187            case etNumber:
4188            case etFloat:
4189                node.getStartToken().setTextWithBackup("999");
4190                break;
4191            case etString:
4192                node.getStartToken().setTextWithBackup("'placeholder_str'");
4193                break;
4194        }
4195    }
4196
4197}