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