001/*
002 * Decompiled with CFR 0.152.
003 */
004package gudusoft.gsqlparser.parser;
005
006import gudusoft.gsqlparser.EDataType;
007import gudusoft.gsqlparser.EDbVendor;
008import gudusoft.gsqlparser.EErrorType;
009import gudusoft.gsqlparser.EFindSqlStateType;
010import gudusoft.gsqlparser.ESqlStatementType;
011import gudusoft.gsqlparser.ETokenType;
012import gudusoft.gsqlparser.TBaseType;
013import gudusoft.gsqlparser.TCustomLexer;
014import gudusoft.gsqlparser.TCustomParser;
015import gudusoft.gsqlparser.TCustomSqlStatement;
016import gudusoft.gsqlparser.TLexerTeradata;
017import gudusoft.gsqlparser.TParserTeradata;
018import gudusoft.gsqlparser.TSourceToken;
019import gudusoft.gsqlparser.TSourceTokenList;
020import gudusoft.gsqlparser.TStatementList;
021import gudusoft.gsqlparser.TSyntaxError;
022import gudusoft.gsqlparser.nodes.TDatatypeAttribute;
023import gudusoft.gsqlparser.nodes.TFunctionCall;
024import gudusoft.gsqlparser.nodes.TTypeName;
025import gudusoft.gsqlparser.nodes.teradata.TTeradataHelper;
026import gudusoft.gsqlparser.parser.AbstractSqlParser;
027import gudusoft.gsqlparser.parser.ParserContext;
028import gudusoft.gsqlparser.parser.SqlParseResult;
029import gudusoft.gsqlparser.resolver.TSQLResolver;
030import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory;
031import gudusoft.gsqlparser.stmt.TSetStmt;
032import gudusoft.gsqlparser.stmt.TUnknownSqlStatement;
033import gudusoft.gsqlparser.stmt.teradata.TTeradataBTEQCmd;
034import gudusoft.gsqlparser.stmt.teradata.TTeradataFastExportCmd;
035import gudusoft.gsqlparser.stmt.teradata.TTeradataFastLoadCmd;
036import gudusoft.gsqlparser.stmt.teradata.TTeradataMultiLoadCmd;
037import gudusoft.gsqlparser.stmt.teradata.utilities.BteqCmdType;
038import java.util.ArrayList;
039
040public class TeradataSqlParser
041extends AbstractSqlParser {
042    public TLexerTeradata flexer;
043    private TParserTeradata fparser;
044    private TCustomSqlStatement gcurrentsqlstatement;
045    private char curdelimiterchar = (char)59;
046
047    // BTEQ state tracking - must persist across token processing
048    private boolean inBTEQComment = false;
049    private boolean isContinueBTEQCmd = false;
050    // Track when we're collecting a SQL statement after .IF/.ELSEIF THEN
051    private boolean isInIfThenStatement = false;
052
053    // token code of the TRUNC keyword (no TBaseType constant exists for it)
054    private final int truncTokenCode;
055
056    public TeradataSqlParser() {
057        super(EDbVendor.dbvteradata);
058        this.delimiterChar = '/';  // Teradata uses forward slash as delimiter for stored procedures
059        this.defaultDelimiterStr = ";";  // Match TGSqlParser default
060        this.flexer = new TLexerTeradata();
061        this.flexer.delimiterchar = '/';  // Must match TGSqlParser: delimiterchar = '/'
062        this.flexer.defaultDelimiterStr = ";";  // Match TGSqlParser default
063        this.lexer = this.flexer;
064        this.fparser = new TParserTeradata(null);
065        this.fparser.lexer = this.flexer;
066        this.truncTokenCode = this.flexer.getkeywordvalue("TRUNC");
067    }
068
069    @Override
070    protected TCustomLexer getLexer(ParserContext context) {
071        // Transfer Teradata utility type from context to lexer
072        if (context.getTeradataUtilityType() != null) {
073            this.flexer.setTeradataUtilityType(context.getTeradataUtilityType());
074        }
075        return this.flexer;
076    }
077
078    @Override
079    protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) {
080        this.fparser.sourcetokenlist = tokens;
081        return this.fparser;
082    }
083
084    @Override
085    protected TCustomParser getSecondaryParser(ParserContext context, TSourceTokenList tokens) {
086        return null;
087    }
088
089    @Override
090    protected void tokenizeVendorSql() {
091        this.doteradatatexttotokenlist();
092    }
093
094    @Override
095    protected void setupVendorParsersForExtraction() {
096        this.fparser.sqlcmds = this.sqlcmds;
097        this.fparser.sourcetokenlist = this.sourcetokenlist;
098    }
099
100    @Override
101    protected void extractVendorRawStatements(SqlParseResult.Builder builder) {
102        this.doteradatagetrawsqlstatements(builder);
103    }
104
105    private void doteradatatexttotokenlist() {
106        TSourceToken asourcetoken, lcprevst;
107        int yychar;
108
109        asourcetoken = this.getanewsourcetoken();
110        if (asourcetoken == null) {
111            return;
112        }
113        yychar = asourcetoken.tokencode;
114
115        while (yychar > 0) {
116            this.sourcetokenlist.add(asourcetoken);
117            asourcetoken = this.getanewsourcetoken();
118            if (asourcetoken == null) break;
119
120            if ((asourcetoken.tokencode == TBaseType.rrw_casespecific)
121                    || (asourcetoken.tokencode == TBaseType.rrw_teradata_cs)) {
122                // change not to not1 to make "not null column constraints" work correctly
123                // |RW_NOT1 RW_CASESPECIFIC
124                lcprevst = this.getprevsolidtoken(asourcetoken);
125                if (lcprevst != null) {
126                    if (lcprevst.tokencode == TBaseType.rrw_not) {
127                        lcprevst.tokencode = TBaseType.rw_not1;
128                    }
129                }
130            } else if (asourcetoken.tokencode == ';') {
131                lcprevst = this.getprevsolidtoken(asourcetoken);
132                if (lcprevst != null) {
133                    if (lcprevst.tokencode == ';') {
134                        asourcetoken.tokencode = TBaseType.sqlpluscmd;
135                    }
136                }
137            } else if (asourcetoken.tokencode == TBaseType.variable) { // mantisbt/view.php?id=972
138                if (asourcetoken.toString().toLowerCase(java.util.Locale.ROOT).endsWith("begin")) { // MYPROC :BEGIN
139                    asourcetoken.tokencode = TBaseType.rrw_begin;
140                    asourcetoken.tokentype = ETokenType.ttkeyword;
141
142                    lcprevst = this.getprevsolidtoken(asourcetoken);
143                    if (lcprevst != null) {
144                        lcprevst.tokencode = TBaseType.mslabel;
145                    }
146                }
147            } else if (asourcetoken.tokencode == TCustomLexer.UNICODE_ENCODE_ID) {
148                if (asourcetoken.toString().endsWith("008D")) { // REVERSE LINE FEED, https://codepoints.net/U+008D?lang=en
149                    asourcetoken.tokencode = TBaseType.lexspace;
150                } else {
151                    asourcetoken.tokencode = TBaseType.ident;
152                }
153            }
154
155            yychar = asourcetoken.tokencode;
156        }
157    }
158
159    private TSourceToken getprevsolidtoken(TSourceToken ptoken) {
160        TSourceToken ret = null;
161        TSourceTokenList lctokenlist = ptoken.container;
162        if (lctokenlist != null && ptoken.posinlist > 0 && lctokenlist.size() > ptoken.posinlist - 1) {
163            ret = lctokenlist.get((int)(ptoken.posinlist - 1)).tokentype != ETokenType.ttwhitespace && lctokenlist.get((int)(ptoken.posinlist - 1)).tokentype != ETokenType.ttreturn && lctokenlist.get((int)(ptoken.posinlist - 1)).tokentype != ETokenType.ttsimplecomment && lctokenlist.get((int)(ptoken.posinlist - 1)).tokentype != ETokenType.ttbracketedcomment ? lctokenlist.get(ptoken.posinlist - 1) : lctokenlist.nextsolidtoken(ptoken.posinlist - 1, -1, false);
164        }
165        return ret;
166    }
167
168    private int doteradatagetrawsqlstatements(SqlParseResult.Builder builder) {
169        this.gcurrentsqlstatement = null;
170        EFindSqlStateType gst = EFindSqlStateType.stnormal;
171        int lcNestedParens = 0;
172        int lcNestedBeginEnd = 0;
173        int lcNestedCase = 0;
174        int lcNestedSample = 0;
175        Object lcprevst = null;
176        Object lcnextst = null;
177        Object lcnextst2 = null;
178        Object lcnextst3 = null;
179        TSourceToken lcprevsolidtoken = null;
180        TSourceToken ast = null;
181        int errorcount = 0;
182        // Note: inBTEQComment, isContinueBTEQCmd, isInIfThenStatement are instance fields
183        this.inBTEQComment = false;
184        this.isContinueBTEQCmd = false;
185        this.isInIfThenStatement = false;
186        char ch;
187        String lcstr;
188        block10: for (int i = 0; i < this.sourcetokenlist.size(); ++i) {
189            if (ast != null && ast.issolidtoken()) {
190                lcprevsolidtoken = ast;
191            }
192            ast = this.sourcetokenlist.get(i);
193            this.sourcetokenlist.curpos = i;
194            if (ast.tokencode != TBaseType.rrw_time && ast.tokencode != TBaseType.rrw_date && ast.tokencode != TBaseType.rrw_timestamp) {
195                if (ast.tokencode == '(') {
196                    this.handleLeftParenthesisToken(ast, i);
197                } else if (ast.tokencode == TBaseType.rrw_for) {
198                    this.handleForToken(ast);
199                } else if (ast.tokencode == TBaseType.rrw_teradata_last) {
200                    this.handleLastToken(ast);
201                } else if (ast.tokencode == TBaseType.rrw_teradata_pivot) {
202                    this.handlePivotToken(ast);
203                } else if (ast.tokencode == TBaseType.rrw_teradata_period) {
204                    this.handlePeriodToken(ast);
205                } else if (ast.tokencode == TBaseType.rrw_teradata_transaction) {
206                    this.handleTransactionToken(ast);
207                } else if (ast.tokencode == TBaseType.rrw_replace) {
208                    this.handleReplaceToken(ast);
209                } else if (ast.tokencode == TBaseType.rrw_between) {
210                    this.handleBetweenToken(ast);
211                } else if (ast.tokencode == TBaseType.rrw_declare) {
212                    this.handleDeclareToken(ast);
213                } else if (ast.tokencode == TBaseType.rrw_case && ast.tag == 0 && ast.posinlist + 1 < this.sourcetokenlist.size()) {
214                    this.handleCaseToken(ast);
215                } else if (ast.tokencode == TBaseType.rrw_grant) {
216                    this.handleGrantToken(ast);
217                } else if (ast.tokencode == TBaseType.variable) {
218                    this.handleVariableToken(ast);
219                } else if (ast.tokencode == TBaseType.rrw_teradata_sequenced || ast.tokencode == TBaseType.rrw_teradata_nonsequenced) {
220                    this.handleSequencedToken(ast);
221                }
222            }
223            switch (gst) {
224                case sterror: {
225                    if (ast.tokentype == ETokenType.ttsemicolon) {
226                        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
227                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
228                        gst = EFindSqlStateType.stnormal;
229                        continue block10;
230                    }
231                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
232                    continue block10;
233                }
234                case stMultiLoadCmd: {
235                    gst = this.handleMultiLoadCmdState(ast, gst, builder);
236                    continue block10;
237                }
238                case stFastExportCmd: {
239                    gst = this.handleFastExportCmdState(ast, gst, builder);
240                    continue block10;
241                }
242                case stFastLoadCmd: {
243                    gst = this.handleFastLoadCmdState(ast, gst, builder);
244                    continue block10;
245                }
246                case stBTEQCmd: {
247                    gst = this.handleBTEQCmdState(ast, builder);
248                    continue block10;
249                }
250                case stnormal: {
251                    gst = this.handleNormalState(ast, gst, lcprevsolidtoken, builder);
252                    if (gst != EFindSqlStateType.ststoredprocedure) continue block10;
253                    lcNestedParens = 0;
254                    continue block10;
255                }
256                case stsql: {
257                    boolean readyToEnd = true;
258                    if (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatemacro) {
259                        if (ast.tokencode == '(' || ast.tokencode == TBaseType.left_parenthesis_2) {
260                            ++lcNestedParens;
261                        }
262                        if (ast.tokencode == ')') {
263                            --lcNestedParens;
264                            if (lcNestedParens < 0) {
265                                lcNestedParens = 0;
266                            }
267                        }
268                        readyToEnd = lcNestedParens == 0;
269                    }
270                    if (ast.tokentype == ETokenType.ttsemicolon && readyToEnd) {
271                        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
272                        this.gcurrentsqlstatement.semicolonended = ast;
273                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
274                        gst = EFindSqlStateType.stnormal;
275                        continue block10;
276                    }
277                    if (ast.tokencode == TBaseType.cmtdoublehyphen && ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) {
278                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
279                        gst = EFindSqlStateType.stnormal;
280                        continue block10;
281                    }
282                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
283                    continue block10;
284                }
285                case ststoredprocedure: {
286                    boolean readyToEnd = true;
287                    if (ast.tokencode == ';') {
288                        // don't count case..end, sample ... end after ;
289                        lcNestedSample = 0;
290                        lcNestedCase = 0;
291                    } else if (ast.tokencode == TBaseType.rrw_case) { // case ... end
292                        ++lcNestedCase;
293                        TSourceToken prevst = ast.prevSolidToken();
294                        if (prevst != null && prevst.tokencode == TBaseType.rrw_end) {
295                            // don't count: end case
296                            --lcNestedCase;
297                        }
298                    } else if (ast.tokencode == TBaseType.rrw_teradata_sample) { // sample ... end
299                        ++lcNestedSample;
300                    }
301                    if (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger
302                            || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure
303                            || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatefunction
304                            || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatefunction) {
305                        if (ast.tokencode == '(' || ast.tokencode == TBaseType.left_parenthesis_2) {
306                            ++lcNestedParens;
307                        }
308                        if (ast.tokencode == ')' && --lcNestedParens < 0) {
309                            lcNestedParens = 0;
310                        }
311                        if (ast.tokencode == TBaseType.rrw_begin) {
312                            ++lcNestedBeginEnd;
313                        }
314                        if (ast.tokencode == TBaseType.rrw_end) {
315                            boolean countThisEnd = true;
316                            if (lcNestedSample > 0) {
317                                // don't count sample...end
318                                --lcNestedSample;
319                                countThisEnd = false;
320                            } else if (lcNestedCase > 0) {
321                                // don't count case...end
322                                --lcNestedCase;
323                                countThisEnd = false;
324                            }
325                            if (countThisEnd) {
326                                // don't count end while/if/case/loop/for/repeat
327                                TSourceToken nt = ast.nextSolidToken();
328                                if (nt != null) {
329                                    countThisEnd = nt.tokencode != TBaseType.rrw_while
330                                            && nt.tokencode != TBaseType.rrw_if
331                                            && nt.tokencode != TBaseType.rrw_case
332                                            && nt.tokencode != TBaseType.rrw_loop
333                                            && nt.tokencode != TBaseType.rrw_for
334                                            && nt.tokencode != TBaseType.rrw_repeat;
335                                }
336                            }
337                            if (countThisEnd && --lcNestedBeginEnd < 0) {
338                                lcNestedBeginEnd = 0;
339                            }
340                        }
341                        readyToEnd = lcNestedParens == 0 && lcNestedBeginEnd == 0;
342                    }
343                    // single stmt in function/procedure/trigger may use ; as terminate char
344                    // so default terminate char is ;, if begin is found, then
345                    // set terminate char to DelimiterChar
346                    if (this.curdelimiterchar != this.delimiterChar) {
347                        if (ast.tokencode == TBaseType.rrw_begin) {
348                            this.curdelimiterchar = this.delimiterChar;
349                        } else if (ast.tokencode == TBaseType.mslabel && ast.container.nextsolidtoken(ast, 1, false).tokencode == TBaseType.rrw_begin) {
350                            this.curdelimiterchar = this.delimiterChar;
351                        }
352                    }
353                    if (this.curdelimiterchar == ';') {
354                        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
355                        if (ast.tokentype == ETokenType.ttsemicolon
356                                && (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger
357                                    || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure
358                                    || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatefunction)
359                                && readyToEnd) {
360                            gst = EFindSqlStateType.stnormal;
361                            this.gcurrentsqlstatement.semicolonended = ast;
362                            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
363                            continue block10;
364                        }
365                    } else {
366                        if (ast.getAstext().length() == 1) {
367                            ch = ast.getAstext().charAt(0);
368                        } else if (ast.getAstext().length() > 1) {
369                            ch = ast.getAstext().charAt(ast.getAstext().length() - 1);
370                        } else {
371                            ch = ' ';
372                        }
373                        if (ast.tokencode != TBaseType.cmtslashstar && ast.tokencode != TBaseType.cmtdoublehyphen
374                                && ch == this.curdelimiterchar && ast.isFirstTokenOfLine() && readyToEnd) {
375                            if (ast.getAstext().length() > 1) {
376                                lcstr = ast.getAstext().substring(0, ast.getAstext().length() - 1);
377                                int c2 = this.flexer.getkeywordvalue(lcstr);
378                                if (c2 > 0) {
379                                    ast.tokencode = c2;
380                                }
381                            } else {
382                                this.gcurrentsqlstatement.semicolonended = ast;
383                            }
384                            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
385                            gst = EFindSqlStateType.stnormal;
386                            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
387                        } else if (ast.searchToken(TBaseType.rrw_create, 1) != null && ast.searchToken(TBaseType.rrw_procedure, 4) != null && readyToEnd) {
388                            gst = EFindSqlStateType.stnormal;
389                            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
390                        } else if (ast.tokencode == ';') {
391                            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
392                            boolean startNewStatement = false;
393                            if (readyToEnd) {
394                                startNewStatement = this.sqlcmds.issql(ast.nextSolidToken(), gst, this.gcurrentsqlstatement) != null;
395                            }
396                            if (startNewStatement) {
397                                gst = EFindSqlStateType.stnormal;
398                                this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
399                            }
400                        } else {
401                            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
402                        }
403                    }
404                    if (gst == EFindSqlStateType.ststoredprocedure && ast.tokencode == TBaseType.cmtdoublehyphen && ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) {
405                        gst = EFindSqlStateType.stnormal;
406                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
407                    }
408                }
409            }
410        }
411        if (TBaseType.assigned(this.gcurrentsqlstatement) && (gst == EFindSqlStateType.stsql || gst == EFindSqlStateType.stBTEQCmd || gst == EFindSqlStateType.ststoredprocedure || gst == EFindSqlStateType.sterror
412                || gst == EFindSqlStateType.stFastExportCmd || gst == EFindSqlStateType.stFastLoadCmd || gst == EFindSqlStateType.stMultiLoadCmd)) {
413            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder);
414        }
415        builder.sqlStatements(this.sqlstatements);
416        builder.syntaxErrors(this.syntaxErrors instanceof ArrayList ? (ArrayList)this.syntaxErrors : new ArrayList(this.syntaxErrors));
417        builder.errorCode(this.syntaxErrors.isEmpty() ? 0 : this.syntaxErrors.size());
418        return errorcount;
419    }
420
421    private void handleLeftParenthesisToken(TSourceToken ast, int i) {
422        TSourceToken lcnextst = this.sourcetokenlist.nextsolidtoken(i, 1, false);
423        TSourceToken lcprevst = this.getprevsolidtoken(ast);
424        if ((lcprevst != null) && (
425                (lcprevst.tokencode == TBaseType.rrw_teradata_cast)
426                || (lcprevst.tokencode == TBaseType.rrw_year) // sample sql: select year(date) - 1 mantisbt/view.php?id=3049
427                || ((this.truncTokenCode > 0) && (lcprevst.tokencode == this.truncTokenCode)) // trunc(date): TRUNC of the current date, Mantis 4947
428        )) {
429            lcnextst = null; // don't check next token if prev token is cast. sample: cast(DATETIME as BIGINT)
430        }
431        if ((lcprevst != null) && (TFunctionCall.isBuiltIn(lcprevst.toString(), EDbVendor.dbvteradata))) {
432            if (((lcprevst.tokencode == TBaseType.rrw_end)
433                    || (lcprevst.tokencode == TBaseType.rrw_time)
434                    || (lcprevst.tokencode == TBaseType.rrw_date)
435                    || (lcprevst.tokencode == TBaseType.rrw_timestamp)
436                    || (lcprevst.tokencode == TBaseType.rrw_teradata_type)
437                    || (lcprevst.toString().equalsIgnoreCase("current_date"))
438                    || (lcprevst.toString().equalsIgnoreCase("current_time"))
439                    || (lcprevst.toString().equalsIgnoreCase("current_timestamp"))
440                    || (lcprevst.toString().equalsIgnoreCase("user"))
441                    || (lcprevst.toString().equalsIgnoreCase("current_user"))
442            )) {
443                // Keep lcnextst as is
444            } else {
445                lcnextst = null;
446            }
447        }
448        if (lcnextst != null) {
449            EDataType dataType = TTypeName.searchTeradataTypeByName(lcnextst.toString());
450            if ((dataType == null) && (lcnextst.tokencode == TBaseType.rrw_double)
451                    && !followsFunctionName(lcprevst)) {
452                // DOUBLE PRECISION is a type name of two words, so x (DOUBLE PRECISION) is
453                // cast shorthand like x (FLOAT) (Mantis 4952). The parameter list of
454                // CREATE FUNCTION f (DOUBLE PRECISION) keeps its plain parenthesis.
455                TSourceToken precision = this.sourcetokenlist.nextsolidtoken(i, 2, false);
456                if ((precision != null) && (precision.tokencode == TBaseType.rrw_precision)) {
457                    dataType = EDataType.double_precision_t;
458                }
459            }
460            if ((dataType != null) && (lcprevst != null) && (lcprevst.tokencode != TBaseType.rrw_teradata_period)) {
461                ast.tokencode = TBaseType.rrw_teradata_start_data_conversion;
462                // lcnextst2 is the token 2 steps after (
463                TSourceToken lcnextst2 = this.sourcetokenlist.nextsolidtoken(i, 2, false);
464                // lcnextst3 is the token 3 steps after (
465                TSourceToken lcnextst3 = this.sourcetokenlist.nextsolidtoken(i, 3, false);
466                if (lcnextst2.tokencode == ',') {
467                    // this is data conversion: (date,format '$xxx'),
468                    // this is not data conversion: trunc(date,'yy')
469                    TSourceToken st1 = lcnextst3;
470                    TSourceToken st2 = this.sourcetokenlist.nextsolidtoken(i, 4, false);
471                    if (TDatatypeAttribute.searchDataTypeAttributeByName(st1, st2) == null) {
472                        // this is not data conversion, such as :trunc(date,'yy')
473                        ast.tokencode = '(';
474                    }
475                } else if (lcnextst2 != null) {
476                    // lcnextst3 may be null when (date) is at end of query
477                    this.handleDataTypeConversion(ast, dataType, lcnextst2, lcnextst3, lcprevst);
478                }
479            } else {
480                TSourceToken st1 = lcnextst;
481                TSourceToken st2 = this.sourcetokenlist.nextsolidtoken(i, 2, false);
482                if (TDatatypeAttribute.searchDataTypeAttributeByName(st1, st2) != null) {
483                    ast.tokencode = TBaseType.rrw_teradata_start_data_conversion;
484                }
485            }
486        }
487    }
488
489    /**
490     * True when {@code nameEnd} ends a routine name written right after FUNCTION, such as
491     * {@code f} or {@code db1.f} in {@code CREATE FUNCTION db1.f (...)}: the parenthesis after it
492     * opens the parameter list.
493     */
494    private static boolean followsFunctionName(TSourceToken nameEnd) {
495        if (nameEnd == null) return false;
496        TSourceToken before = nameEnd.prevSolidToken();
497        while ((before != null) && (before.tokencode == '.')) {
498            TSourceToken part = before.prevSolidToken();
499            before = (part == null) ? null : part.prevSolidToken();
500        }
501        return (before != null) && (before.tokencode == TBaseType.rrw_function);
502    }
503
504    private void handleDataTypeConversion(TSourceToken ast, EDataType dataType, TSourceToken lcnextst2, TSourceToken lcnextst3, TSourceToken lcprevst) {
505        switch (dataType) {
506            case date_t:
507            case time_t:
508                // next token is: ) or comma or data attribute token
509                if ((lcnextst2.tokencode == ')')
510                        || ((lcnextst2.tokencode == '(') && (lcnextst3 != null) && (lcnextst3.tokencode == TBaseType.iconst))) { // hire_date (time(6))
511                    // ident(DATE) / ident(TIME): keep the data-conversion reading.
512                    // Teradata documents column_name(data_type) as CAST shorthand, and
513                    // whitespace before '(' is not significant, so col1(DATE) and
514                    // col1 (DATE) must produce the same parse tree (Mantis 4647 - the
515                    // earlier whitespace-based disambiguation from Mantis 4023 silently
516                    // corrupted JOIN predicates like o.order_ts(DATE)).
517                    // The genuinely ambiguous case - a KNOWN function taking the bare
518                    // DATE/TIME literal, e.g. TD_WEDNESDAY(date) - never reaches this
519                    // branch: built-in function names (including the TD_* day functions
520                    // in teradata_function.properties) are filtered by the
521                    // TFunctionCall.isBuiltIn check above, which leaves their '('
522                    // untouched so they parse as function calls.
523                } else {
524                    ast.tokencode = '(';
525                }
526                break;
527            case timestamp_t:
528            case interval_t:
529                if (lcnextst2.tokencode == TBaseType.sconst) { // INSERT t1 (TIME '10:44:25.123-08:00',TIMESTAMP '2000-09-20 10:44:25.1234')
530                    ast.tokencode = '(';
531                }
532                break;
533            case period_t:
534                // PERIOD(DATE), PERIOD(TIME(6)), PERIOD(TIMESTAMP(6) WITH TIME ZONE) name the
535                // PERIOD data type, so x (PERIOD(DATE)) is cast shorthand. PERIOD(T.B, T.E) and
536                // PERIOD(DATE '2021-01-01', ...) call the PERIOD constructor: a '(' in front of
537                // them is an ordinary parenthesis, as in SELECT (PERIOD(T.B, T.E)) (Mantis 4906).
538                if ((lcnextst2.tokencode == '(') && isPeriodElementType(lcnextst3)) {
539                    // keep the data-conversion reading
540                } else {
541                    ast.tokencode = '(';
542                }
543                break;
544            case char_t:
545                if (lcnextst2.tokencode == ')') {
546                    // NULL (CHAR) AS B
547                } else if ((lcnextst3 == null) || (lcnextst3.tokencode != TBaseType.iconst)) { // INSERT t1 (TIME '10:44:25.123-08:00',TIMESTAMP '2000-09-20 10:44:25.1234')
548                    ast.tokencode = '(';
549                }
550                break;
551            default:
552                // A cast-shorthand like (NUMBER(10,2)) always carries an integer
553                // constant as its first precision argument. When the token after the
554                // type-name's '(' is anything else — e.g. a macro/procedure parameter
555                // named like a type: CREATE MACRO m (number (INTEGER)) — this is not
556                // a data conversion, so restore the plain parenthesis.
557                if ((lcnextst2 != null) && (lcnextst2.tokencode == '(')
558                        && ((lcnextst3 == null) || (lcnextst3.tokencode != TBaseType.iconst))) {
559                    ast.tokencode = '(';
560                }
561                break;
562        }
563    }
564
565    /**
566     * True when {@code tok} starts the element type of a PERIOD data type: {@code DATE)},
567     * {@code TIME)} / {@code TIMESTAMP)}, either of them with a precision {@code (n)}, or with
568     * {@code WITH TIME ZONE}. A DATE/TIME/TIMESTAMP literal ({@code DATE '2021-01-01'}), a
569     * DATE/TIME/TIMESTAMP function call ({@code DATE(t.b)}) or a column reference is not.
570     */
571    private static boolean isPeriodElementType(TSourceToken tok) {
572        if (tok == null) return false;
573        TSourceToken next = tok.nextSolidToken();
574        if (next == null) return false;
575        if (tok.tokencode == TBaseType.rrw_date) {
576            return next.tokencode == ')';
577        }
578        if ((tok.tokencode != TBaseType.rrw_time) && (tok.tokencode != TBaseType.rrw_timestamp)) {
579            return false;
580        }
581        if (next.tokencode == '(') {
582            TSourceToken precision = next.nextSolidToken();
583            TSourceToken close = (precision == null) ? null : precision.nextSolidToken();
584            return (precision != null) && (precision.tokencode == TBaseType.iconst)
585                    && (close != null) && (close.tokencode == ')');
586        }
587        return (next.tokencode == ')') || (next.tokencode == TBaseType.rrw_with);
588    }
589
590    private void handleForToken(TSourceToken ast) {
591        TSourceToken nextToken = ast.searchTokenAfterObjectName();
592        if (nextToken != null) {
593            if (nextToken.tokencode == TBaseType.rrw_as) {
594                ast.tokencode = TBaseType.rrw_teradata_for_loop;
595            }
596        }
597    }
598
599    private void handleLastToken(TSourceToken ast) {
600        TSourceToken nextToken = ast.searchToken('(', 1);
601        if (nextToken != null) {
602            ast.tokencode = TBaseType.rrw_last_function;
603        }
604    }
605
606    private void handlePivotToken(TSourceToken ast) {
607        TSourceToken nextToken = ast.searchToken('(', 1);
608        if (nextToken == null) {
609            ast.tokencode = TBaseType.ident;
610        }
611    }
612
613    private void handlePeriodToken(TSourceToken ast) {
614        TSourceToken nextToken = ast.nextSolidToken();
615        if (nextToken == null) {
616            ast.tokencode = TBaseType.ident;
617        } else if (nextToken.tokencode == '(') {
618            // keep it as keyword: PERIOD(...) is the PERIOD constructor or data type. But a data
619            // type attribute after the '(' makes period a column carrying that attribute, as in
620            // SELECT period (NAMED c, CHAR(17)) (Mantis 4953).
621            TSourceToken st1 = nextToken.nextSolidToken();
622            TSourceToken st2 = (st1 == null) ? null : st1.nextSolidToken();
623            if ((st1 != null) && (TDatatypeAttribute.searchDataTypeAttributeByName(st1, st2) != null)) {
624                ast.tokencode = TBaseType.ident;
625            }
626        } else if (nextToken.tokencode == TBaseType.rrw_for) {
627            // keep it as keyword
628        } else if (nextToken.tokencode == TBaseType.sconst) {
629            // sample: DEFAULT PERIOD '(2005-02-03, 2006-02-03)'
630            // keep it as keyword
631        } else {
632            if (ast.prevSolidToken() == null) {
633                ast.tokencode = TBaseType.ident;
634            } else {
635                if (!ast.prevSolidToken().toString().equalsIgnoreCase("anchor")) {
636                    ast.tokencode = TBaseType.ident;
637                }
638            }
639        }
640    }
641
642    private void handleTransactionToken(TSourceToken ast) {
643        TSourceToken lcprevst = this.getprevsolidtoken(ast);
644        if (lcprevst != null) {
645            if (lcprevst.tokencode == TBaseType.rrw_end) {
646                lcprevst.tokencode = TBaseType.rrw_teradata_end_t;
647            }
648        }
649    }
650
651    private void handleReplaceToken(TSourceToken ast) {
652        TSourceToken nextToken = ast.searchToken('(', 1);
653        if (nextToken != null) {
654            ast.tokencode = TBaseType.ident;
655        }
656    }
657
658    private void handleBetweenToken(TSourceToken ast) {
659        TSourceToken nextToken = ast.nextSolidToken();
660        if (nextToken != null) {
661            if ((nextToken.tokencode == TBaseType.rrw_begin) || (nextToken.tokencode == TBaseType.rrw_end)) {
662                // BETWEEN begin(DODD_EFF_PERI)
663                nextToken.tokencode = TBaseType.ident;
664            }
665        }
666    }
667
668    private void handleDeclareToken(TSourceToken ast) {
669        TSourceToken nextToken = ast.nextSolidToken();
670        if (nextToken != null) {
671            TSourceToken nextnextToken = nextToken.nextSolidToken();
672            if (nextnextToken != null) {
673                if ((nextnextToken.toString().equalsIgnoreCase("cursor"))
674                        || (nextnextToken.toString().equalsIgnoreCase("insensitive"))
675                        || (nextnextToken.toString().equalsIgnoreCase("scroll"))
676                        || (nextnextToken.toString().equalsIgnoreCase("no"))) {
677                    nextToken.tokencode = TBaseType.rrw_teradata_cursor_name;
678                } else if ((nextnextToken.toString().equalsIgnoreCase("condition"))) {
679                    nextToken.tokencode = TBaseType.rrw_teradata_condition_name;
680                }
681            }
682        }
683    }
684
685    private void handleCaseToken(TSourceToken ast) {
686        int lcNest = 1;
687        for (int m = ast.posinlist + 1; m < this.sourcetokenlist.size() - 1; m++) {
688            TSourceToken st = this.sourcetokenlist.get(m);
689            if (st.tokencode == TBaseType.rrw_case) {
690                st.tag = 1; // this is nested case keyword, don't check this token later
691                lcNest++;
692            } else if (st.tokencode == TBaseType.rrw_begin) {
693                // BEGIN...END blocks consume an END token, track them
694                lcNest++;
695            }
696            if (st.tokencode == TBaseType.rrw_end) {
697                TSourceToken nextSolid = st.nextSolidToken();
698                // END IF, END WHILE, END LOOP, END FOR, END REPEAT close their own blocks,
699                // not our CASE — skip them entirely
700                if (nextSolid != null && (nextSolid.tokencode == TBaseType.rrw_if
701                        || nextSolid.tokencode == TBaseType.rrw_while
702                        || nextSolid.tokencode == TBaseType.rrw_loop
703                        || nextSolid.tokencode == TBaseType.rrw_for
704                        || nextSolid.tokencode == TBaseType.rrw_repeat)) {
705                    continue;
706                }
707                lcNest--;
708                if (lcNest == 0) {
709                    if (nextSolid != null) {
710                        if (nextSolid.tokencode == TBaseType.rrw_case) {
711                            ast.tokencode = TBaseType.rrw_teradata_case_stmt;
712                            nextSolid.tag = 1; // this is case after end, don't check this token later
713                        }
714                    }
715                    // Always break after finding the matching END, whether it's followed by CASE or not
716                    // This prevents CASE expressions from incorrectly matching with later "END CASE" constructs
717                    break;
718                }
719            }
720        }
721    }
722
723    private void handleGrantToken(TSourceToken ast) {
724        TSourceToken prevSolidToken = ast.prevSolidToken();
725        if (prevSolidToken != null) {
726            if (prevSolidToken.tokencode == TBaseType.rrw_with) {
727                prevSolidToken.tokencode = TBaseType.rrw_teradata_with_grant;
728            }
729        }
730    }
731
732    private void handleVariableToken(TSourceToken ast) {
733        // USING ( _spVV8 VARCHAR(1024) CHARACTER SET UNICODE ) SELECT INTO :_spVV8 ;
734        // variable after into keyword treat as identifier
735        TSourceToken prevSolidToken = ast.prevSolidToken();
736        if (prevSolidToken != null) {
737            if (prevSolidToken.tokencode == TBaseType.rrw_into) {
738                ast.tokencode = TBaseType.ident;
739            }
740        }
741    }
742
743    private void handleSequencedToken(TSourceToken ast) {
744        TSourceToken nextSolidToken = ast.nextSolidToken();
745        if (nextSolidToken != null) {
746            if ((nextSolidToken.tokencode == TBaseType.rrw_teradata_validtime)) {
747                TSourceToken nextToken2 = nextSolidToken.nextSolidToken();
748                if (nextToken2 != null) {
749                    if ((nextToken2.tokencode == TBaseType.rrw_select)
750                            || (nextToken2.tokencode == TBaseType.rrw_insert)
751                            || (nextToken2.tokencode == TBaseType.rrw_update)
752                            || (nextToken2.tokencode == TBaseType.rrw_delete)
753                            || (nextToken2.tokencode == TBaseType.rrw_merge)) {
754                        // SEQUENCED VALIDTIME select/insert/update/delete/merge
755                        ast.tokencode = TBaseType.lexspace;
756                        nextSolidToken.tokencode = TBaseType.lexspace;
757                    }
758                }
759            }
760        }
761    }
762
763    private EFindSqlStateType handleMultiLoadCmdState(TSourceToken ast, EFindSqlStateType gst, SqlParseResult.Builder builder) {
764        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
765        if ((ast.tokencode == ';') && (ast.isLastTokenOfLine())) {
766            TSourceToken nextToken = ast.nextSolidToken();
767            TSourceToken nextToken2 = nextToken != null ? nextToken.nextSolidToken() : null;
768            if (nextToken != null && nextToken2 != null &&
769                    nextToken.tokencode == '.' &&
770                    nextToken2.toString().equalsIgnoreCase("FIELD") &&
771                    nextToken.isFirstTokenOfLine()) {
772                // Remain in stMultiLoadCmd state
773            } else {
774                this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
775                gst = EFindSqlStateType.stnormal;
776            }
777        } else if ((ast.tokencode == ';') && this.isSqlStyleSetMultiLoadCmd()) {
778            // A SQL-style `SET` directive (e.g. SET QUERY_BAND = '...' FOR SESSION)
779            // is terminated by its own ';' even when it is NOT the last token of
780            // the line. Without this, a statement that follows the ';' on the same
781            // line (SET QUERY_BAND = 'A=1'; SELECT c1 FROM t1;) is swallowed into
782            // the SET directive and never reaches downstream processing (MantisBT
783            // 4584). The ';' inside a band-specification literal is part of an
784            // SCONST token, not a ';' token, so it never triggers this branch.
785            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
786            gst = EFindSqlStateType.stnormal;
787        } else if (ast.isLastTokenOfLine()) {
788            // .IF ERRORCODE <> 0 THEN
789            // INSERT INTO Sou_EMP_Tab
790            //        (1, 'bala');
791            String cmdstr = this.gcurrentsqlstatement.sourcetokenlist.get(0).toString();
792            if (this.gcurrentsqlstatement.sourcetokenlist.size() > 1) {
793                cmdstr = cmdstr + this.gcurrentsqlstatement.sourcetokenlist.get(1).toString();
794            }
795            if ((cmdstr.toLowerCase(java.util.Locale.ROOT).startsWith(".if")) || (cmdstr.toLowerCase(java.util.Locale.ROOT).startsWith(".set")) || (cmdstr.toLowerCase(java.util.Locale.ROOT).startsWith("set"))) {
796                // A SQL-style SET directive (SET QUERY_BAND = '...') may continue on the
797                // next line with FOR SESSION / FOR TRANSACTION and is terminated by its
798                // own ';'. Do not cut it at end-of-line when the next solid token is FOR.
799                TSourceToken continuation = ast.nextSolidToken();
800                boolean sqlSetContinues = this.isSqlStyleSetMultiLoadCmd()
801                        && continuation != null && continuation.tokencode == TBaseType.rrw_for;
802                if (!sqlSetContinues) {
803                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
804                    gst = EFindSqlStateType.stnormal;
805                }
806            }
807        }
808        return gst;
809    }
810
811    /**
812     * True when the MultiLoad-state command currently being collected is a
813     * SQL-style {@code SET} directive — one whose first solid token is the bare
814     * {@code SET} keyword (for example {@code SET QUERY_BAND = '...' FOR SESSION}).
815     * <p>
816     * These directives are session-level SQL statements that terminate at ';',
817     * unlike genuine MultiLoad utility directives which are dot-prefixed
818     * ({@code .SET}, {@code .FIELD}, ...) and are line-based. Restricting the
819     * mid-line ';' termination to bare {@code SET} keeps real dot-commands
820     * untouched.
821     */
822    private boolean isSqlStyleSetMultiLoadCmd() {
823        if (this.gcurrentsqlstatement == null) return false;
824        for (int i = 0; i < this.gcurrentsqlstatement.sourcetokenlist.size(); i++) {
825            TSourceToken t = this.gcurrentsqlstatement.sourcetokenlist.get(i);
826            if (!t.issolidtoken()) continue;
827            return t.tokencode == TBaseType.rrw_set;
828        }
829        return false;
830    }
831
832    private EFindSqlStateType handleFastExportCmdState(TSourceToken ast, EFindSqlStateType gst, SqlParseResult.Builder builder) {
833        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
834        if ((ast.tokencode == ';') && (ast.isLastTokenOfLine())) {
835            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
836            gst = EFindSqlStateType.stnormal;
837        } else {
838            boolean findSQL = false;
839            TSourceToken nextst = ast.nextToken();
840            if (nextst != null) {
841                if (nextst.tokentype == ETokenType.ttreturn) {
842                    findSQL = (this.sqlcmds.issql(ast.nextSolidToken(), gst, this.gcurrentsqlstatement) != null);
843                    if (findSQL) {
844                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
845                        gst = EFindSqlStateType.stnormal;
846                    }
847                }
848            }
849        }
850        return gst;
851    }
852
853    private EFindSqlStateType handleFastLoadCmdState(TSourceToken ast, EFindSqlStateType gst, SqlParseResult.Builder builder) {
854        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
855        if ((ast.tokencode == ';') && (ast.isLastTokenOfLine())) {
856            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
857            gst = EFindSqlStateType.stnormal;
858        } else if (ast.tokencode == TBaseType.lexnewline) {
859            TSourceToken nextst = ast.nextSolidToken();
860            if (nextst != null) {
861                if (nextst.tokencode == TBaseType.BTEQCMD) {
862                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
863                    gst = EFindSqlStateType.stFastLoadCmd;
864                } else if (nextst.tokencode == '.') {
865                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
866                    gst = EFindSqlStateType.stFastLoadCmd;
867                } else if (this.sqlcmds.issql(nextst, gst, this.gcurrentsqlstatement) != null) {
868                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
869                    gst = EFindSqlStateType.stnormal;
870                }
871            }
872        }
873        return gst;
874    }
875
876    private EFindSqlStateType handleBTEQCmdState(TSourceToken ast, SqlParseResult.Builder builder) {
877        EFindSqlStateType gst = EFindSqlStateType.stBTEQCmd;
878        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
879
880        if (ast.tokencode == TBaseType.lexnewline) {
881            if (this.isContinueBTEQCmd) {
882                this.isContinueBTEQCmd = false;
883            } else if (this.isInIfThenStatement) {
884                // Continue collecting tokens for .IF/.ELSEIF THEN until semicolon
885                // Don't complete the statement on newline
886            } else {
887                if (((TTeradataBTEQCmd)this.gcurrentsqlstatement).getBteqCmdType() == BteqCmdType.LOGON) {
888                    boolean findSQL = (this.sqlcmds.issql(ast.nextSolidToken(), gst, this.gcurrentsqlstatement) != null);
889                    if (findSQL) {
890                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
891                        gst = EFindSqlStateType.stnormal;
892                    }
893                } else {
894                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
895                    gst = EFindSqlStateType.stnormal;
896                }
897            }
898        } else if ((ast.tokencode == '-') && (ast.isLastTokenOfLine())) {
899            if (!this.inBTEQComment) {
900                this.isContinueBTEQCmd = true;
901            }
902        } else if (ast.tokencode == TBaseType.lexspace) {
903            // Do nothing
904        } else if (ast.tokencode == ';') {
905            // Reset IF THEN state when statement ends
906            this.isInIfThenStatement = false;
907            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
908            boolean findNewCmdInSameLine = this.checkForNewBTEQCmdInSameLine(ast);
909            if (findNewCmdInSameLine) {
910                gst = EFindSqlStateType.stBTEQCmd;
911            } else {
912                gst = EFindSqlStateType.stnormal;
913            }
914        } else if (ast.tokencode == '*') {
915            this.isContinueBTEQCmd = false;
916        } else { // solid token
917            // Check if this is a THEN keyword at end of line for .IF/.ELSEIF commands
918            // In this case, the BTEQ command should continue to include the following SQL statement
919            BteqCmdType cmdType = ((TTeradataBTEQCmd)this.gcurrentsqlstatement).getBteqCmdType();
920            if ((cmdType == BteqCmdType.IF || cmdType == BteqCmdType.ELSEIF)
921                && ast.tokencode == TBaseType.rrw_then
922                && ast.isLastTokenOfLine()) {
923                this.isContinueBTEQCmd = true;
924                this.isInIfThenStatement = true;
925            } else {
926                this.isContinueBTEQCmd = false;
927            }
928        }
929        return gst;
930    }
931
932    private boolean checkForNewBTEQCmdInSameLine(TSourceToken ast) {
933        boolean findNewCmdInSameLine = false;
934        if (!ast.isLastTokenOfLine()) {
935            // check if next cmd like below:
936            // .SET SIDETITLES ON; .SET FOLDLINE ON ALL
937            TSourceToken nextst = ast.nextSolidToken();
938            if ((nextst != null) && (nextst.tokencode == '.')) {
939                TSourceToken nextst2 = nextst.nextSolidToken();
940                TSourceToken searchToken = null;
941                if (nextst2 != null) {
942                    if (nextst2.tokencode == TBaseType.rrw_set) {
943                        TSourceToken nextst3 = nextst2.nextSolidToken();
944                        if (nextst3 != null) {
945                            searchToken = nextst3;
946                        }
947                    } else {
948                        searchToken = nextst2;
949                    }
950
951                    BteqCmdType bteqCmdType = BteqCmdType.searchBteqCmd(searchToken);
952                    if (bteqCmdType != null) {
953                        this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
954                        ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setBteqCmdType(bteqCmdType);
955                        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
956                        findNewCmdInSameLine = true;
957                    }
958                }
959            }
960        }
961        return findNewCmdInSameLine;
962    }
963
964    private EFindSqlStateType handleNormalState(TSourceToken ast, EFindSqlStateType gst, TSourceToken lcprevsolidtoken, SqlParseResult.Builder builder) {
965        if ((ast.tokencode == TBaseType.cmtdoublehyphen)
966                || (ast.tokencode == TBaseType.cmtslashstar)
967                || (ast.tokencode == TBaseType.lexspace)
968                || (ast.tokencode == TBaseType.lexnewline)
969                || (ast.tokentype == ETokenType.ttsemicolon)) {
970            if (TBaseType.assigned(this.gcurrentsqlstatement)) {
971                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
972            }
973
974            if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
975                if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
976                    // ;;;; continuous semicolon, treat it as comment
977                    ast.tokentype = ETokenType.ttsimplecomment;
978                    ast.tokencode = TBaseType.cmtdoublehyphen;
979                }
980            }
981
982            return gst;
983        }
984        if ((gst = this.checkForBTEQOrUtilityCommand(ast, gst)) != EFindSqlStateType.stnormal) {
985            return gst;
986        }
987
988        // find a tokentext to start sql or stored procedure mode
989        this.gcurrentsqlstatement = this.sqlcmds.issql(ast, gst, this.gcurrentsqlstatement);
990
991        if (TBaseType.assigned(this.gcurrentsqlstatement)) {
992            ESqlStatementType[] ses = {ESqlStatementType.sstcreateprocedure, ESqlStatementType.sstteradatacreatefunction, ESqlStatementType.sstcreatetrigger};
993            if (this.includesqlstatementtype(this.gcurrentsqlstatement.sqlstatementtype, ses)) {
994                gst = EFindSqlStateType.ststoredprocedure;
995                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
996                if ((this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
997                        || (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatefunction)
998                        || (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger)) {
999                    this.curdelimiterchar = ';';
1000                }
1001            } else {
1002                gst = EFindSqlStateType.stsql;
1003                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1004            }
1005        }
1006
1007        if ((!TBaseType.assigned(this.gcurrentsqlstatement)) && (ast.isFirstTokenOfLine())) {
1008            gst = this.flexer.cmdType(ast);
1009            if ((gst == EFindSqlStateType.stMultiLoadCmd) || (gst == EFindSqlStateType.stFastLoadCmd) || (gst == EFindSqlStateType.stFastExportCmd)) {
1010                // SQL-style `SET <variable> = <expression>` is a procedural assignment that
1011                // ends with ';' and may span multiple lines. MultiLoad/BTEQ SET directives do
1012                // not use '=' — if '=' follows the identifier we are looking at a SQL SET.
1013                if (ast.tokencode == TBaseType.rrw_set && isSqlStyleSetAssignment(ast)) {
1014                    this.gcurrentsqlstatement = new TSetStmt(this.vendor);
1015                    this.gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstset;
1016                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1017                    gst = EFindSqlStateType.stsql;
1018                    return gst;
1019                } else {
1020                    this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1021                    ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1022                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1023                    return gst;
1024                }
1025            } else {
1026                gst = EFindSqlStateType.stnormal;
1027            }
1028        }
1029
1030        if (!TBaseType.assigned(this.gcurrentsqlstatement)) { // error tokentext found
1031            this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo),
1032                    "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
1033
1034            ast.tokentype = ETokenType.tttokenlizererrortoken;
1035            gst = EFindSqlStateType.sterror;
1036
1037            this.gcurrentsqlstatement = new TUnknownSqlStatement(this.vendor);
1038            this.gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
1039            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1040        }
1041        return gst;
1042    }
1043
1044    /**
1045     * SQL-style procedural SET variable assignment where the right-hand side
1046     * is a parenthesized expression/subquery: {@code SET <name> = (<expr>)}.
1047     * This is distinct from Teradata BTEQ/MultiLoad SET directives (which use
1048     * space-separated values, no '=', terminate at end-of-line) and from
1049     * SET statements whose tail is vendor-specific BTEQ syntax (for example
1050     * {@code SET QUERY_BAND = '...' UPDATE FOR SESSION}).
1051     * <p>
1052     * Restricting the detection to RHS starting with '(' keeps the fix tightly
1053     * scoped to the reported failure (MantisBT 3336) — a subquery-driven
1054     * assignment that legitimately spans multiple lines and terminates at ';'.
1055     */
1056    private boolean isSqlStyleSetAssignment(TSourceToken setToken) {
1057        TSourceToken next = setToken.nextSolidToken();
1058        if (next == null) return false;
1059        TSourceToken afterNext = next.nextSolidToken();
1060        if (afterNext == null || afterNext.tokencode != '=') return false;
1061        TSourceToken rhs = afterNext.nextSolidToken();
1062        return rhs != null && rhs.tokencode == '(';
1063    }
1064
1065    private EFindSqlStateType checkForBTEQOrUtilityCommand(TSourceToken ast, EFindSqlStateType gst) {
1066        // Check for .bteq commands: either "." at start of line, or "." after ";" at start of line
1067        boolean isDotAtStartOfLine = (ast.tokencode == '.') && (ast.isFirstTokenOfLine());
1068        boolean isDotAfterSemicolonAtStartOfLine = false;
1069        if ((ast.tokencode == '.') && !isDotAtStartOfLine) {
1070            TSourceToken prevToken = ast.prevSolidToken();
1071            if ((prevToken != null) && (prevToken.tokencode == ';') && (prevToken.isFirstTokenOfLine())) {
1072                isDotAfterSemicolonAtStartOfLine = true;
1073            }
1074        }
1075
1076        if (isDotAtStartOfLine || isDotAfterSemicolonAtStartOfLine) {
1077            // .bteq commands
1078            TSourceToken nextst = ast.nextSolidToken();
1079            if (nextst != null) {
1080                gst = this.flexer.cmdType(nextst);
1081                boolean foundCmd = false;
1082                switch (gst) {
1083                    case stMultiLoadCmd:
1084                        this.gcurrentsqlstatement = new TTeradataMultiLoadCmd(this.vendor);
1085                        gst = EFindSqlStateType.stMultiLoadCmd;
1086                        ((TTeradataMultiLoadCmd)this.gcurrentsqlstatement).setCmdType(TTeradataHelper.searchMultiLoadTypeByName(nextst.getAstext()));
1087                        foundCmd = true;
1088                        break;
1089                    case stFastExportCmd:
1090                        this.gcurrentsqlstatement = new TTeradataFastExportCmd(this.vendor);
1091                        gst = EFindSqlStateType.stFastExportCmd;
1092                        foundCmd = true;
1093                        break;
1094                    case stFastLoadCmd:
1095                        this.gcurrentsqlstatement = new TTeradataFastLoadCmd(this.vendor);
1096                        gst = EFindSqlStateType.stFastLoadCmd;
1097                        foundCmd = true;
1098                        break;
1099                    case stBTEQCmd:
1100                        this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1101                        ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setBteqCmdType(BteqCmdType.searchBteqCmd(nextst));
1102                        gst = EFindSqlStateType.stBTEQCmd;
1103                        foundCmd = true;
1104                        break;
1105                    default:
1106                        break;
1107                }
1108                if (foundCmd) {
1109                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1110                    return gst;
1111                }
1112            }
1113        } else if ((ast.tokencode == '*') && (ast.isFirstTokenOfLine())) {
1114            // BTEQ comment: * comment
1115            gst = EFindSqlStateType.stBTEQCmd;
1116            this.inBTEQComment = true;  // Mark that we're in a BTEQ comment
1117            this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1118            ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setBteqCmdType(BteqCmdType.COMMENT);
1119            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1120            return gst;
1121        } else if ((ast.tokencode == '=') && (ast.isFirstTokenOfLine())) {
1122            // BTEQ command starting with =
1123            TSourceToken st1 = ast.prevSolidToken();
1124            if ((st1 != null) && (st1.tokencode == ';')) {
1125                gst = EFindSqlStateType.stBTEQCmd;
1126                this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1127                ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1128                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1129                return gst;
1130            }
1131        } else if ((ast.isFirstTokenOfLine()) && (BteqCmdType.searchBteqCmd(ast) != null)) {
1132            TSourceToken st1 = ast.prevSolidToken();
1133            if ((st1 != null) && (st1.tokencode == ';')) {
1134                gst = EFindSqlStateType.stBTEQCmd;
1135                this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1136                ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1137                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1138                return gst;
1139            }
1140        } else if ((ast.tokencode == '&') && (ast.isFirstTokenOfLine())) {
1141            // Teradata macro/substitution variable: &VARIABLE_NAME at start of line
1142            // Treat it as a BTEQ-like command that ends at end of line
1143            TSourceToken nextst = ast.nextSolidToken();
1144            if (nextst != null && (nextst.tokencode == TBaseType.ident || nextst.tokentype == ETokenType.ttidentifier)) {
1145                gst = EFindSqlStateType.stBTEQCmd;
1146                this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1147                ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1148                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1149                return gst;
1150            }
1151        }
1152        return EFindSqlStateType.stnormal;
1153    }
1154
1155
1156    private boolean includesqlstatementtype(ESqlStatementType type, ESqlStatementType[] types) {
1157        for (ESqlStatementType t : types) {
1158            if (type != t) continue;
1159            return true;
1160        }
1161        return false;
1162    }
1163
1164    @Override
1165    protected TStatementList performParsing(ParserContext context, TCustomParser parser, TCustomParser secondaryParser, TSourceTokenList tokens, TStatementList rawStatements) {
1166        this.fparser = (TParserTeradata)parser;
1167        this.sourcetokenlist = tokens;
1168        this.parserContext = context;
1169        this.sqlstatements = rawStatements;
1170        if (this.sqlstatements == null) {
1171            this.sqlstatements = new TStatementList();
1172            return this.sqlstatements;
1173        }
1174        if (this.sqlcmds == null) {
1175            this.sqlcmds = SqlCmdsFactory.get(this.vendor);
1176        }
1177        this.fparser.sqlcmds = this.sqlcmds;
1178
1179        // Initialize global context for semantic analysis
1180        // CRITICAL: When delegated from TGSqlParser, use TGSqlParser's frameStack
1181        // so that variables set in SET statements can be found by EXECUTE statements
1182        if (context != null && context.getGsqlparser() != null) {
1183            gudusoft.gsqlparser.TGSqlParser gsqlparser = (gudusoft.gsqlparser.TGSqlParser) context.getGsqlparser();
1184            this.frameStack = gsqlparser.getFrameStack();
1185
1186            // CRITICAL: Set gsqlparser on the NodeFactory - matches TGSqlParser behavior
1187            // This is needed for proper AST node creation during parsing
1188            this.fparser.getNf().setGsqlParser(gsqlparser);
1189
1190            // Create global context if needed
1191            this.globalContext = new gudusoft.gsqlparser.compiler.TContext();
1192            this.sqlEnv = new gudusoft.gsqlparser.sqlenv.TSQLEnv(this.vendor) {
1193                @Override
1194                public void initSQLEnv() {
1195                }
1196            };
1197            this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
1198        } else {
1199            this.initializeGlobalContext();
1200        }
1201
1202        for (int i = 0; i < this.sqlstatements.size(); ++i) {
1203            TCustomSqlStatement stmt = this.sqlstatements.getRawSql(i);
1204            try {
1205                stmt.setFrameStack(this.frameStack);
1206                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
1207                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
1208                if (doRecover && (parseResult != 0 || stmt.getErrorCount() > 0)) {
1209                    this.handleCreateTableErrorRecovery(stmt);
1210                }
1211                if (parseResult == 0 && stmt.getErrorCount() <= 0) continue;
1212                this.copyErrorsFromStatement(stmt);
1213                continue;
1214            }
1215            catch (Exception ex) {
1216                ex.printStackTrace();
1217                this.handleStatementParsingException(stmt, i, ex);
1218            }
1219        }
1220        if (this.globalFrame != null) {
1221            this.globalFrame.popMeFromStack(this.frameStack);
1222        }
1223        return this.sqlstatements;
1224    }
1225
1226    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1227        if (!(stmt.sqlstatementtype != ESqlStatementType.sstcreatetable && stmt.sqlstatementtype != ESqlStatementType.sstcreateindex || TBaseType.c_createTableStrictParsing)) {
1228            int nested = 0;
1229            boolean isIgnore = false;
1230            boolean isFoundIgnoreToken = false;
1231            TSourceToken firstIgnoreToken = null;
1232            for (int k = 0; k < stmt.sourcetokenlist.size(); ++k) {
1233                TSourceToken st = stmt.sourcetokenlist.get(k);
1234                if (isIgnore) {
1235                    if (st.issolidtoken() && st.tokencode != 59) {
1236                        isFoundIgnoreToken = true;
1237                        if (firstIgnoreToken == null) {
1238                            firstIgnoreToken = st;
1239                        }
1240                    }
1241                    if (st.tokencode == 59) continue;
1242                    st.tokencode = 273;
1243                    continue;
1244                }
1245                if (st.tokencode == 41 && --nested == 0) {
1246                    isIgnore = true;
1247                }
1248                if (st.tokencode != 40) continue;
1249                ++nested;
1250            }
1251            if (isFoundIgnoreToken) {
1252                stmt.parsestatement(null, false, this.parserContext.isOnlyNeedRawParseTree());
1253            }
1254        }
1255    }
1256
1257    @Override
1258    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
1259        if (!TBaseType.isEnableResolver()) {
1260            return;
1261        }
1262        if (!this.getSyntaxErrors().isEmpty()) {
1263            return;
1264        }
1265        TSQLResolver resolver = new TSQLResolver(this.globalContext, statements);
1266        resolver.resolve();
1267    }
1268
1269    @Override
1270    protected void performInterpreter(ParserContext context, TStatementList statements) {
1271    }
1272
1273    @Override
1274    public String toString() {
1275        return "TeradataSqlParser{vendor=" + (Object)((Object)this.vendor) + "}";
1276    }
1277}