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                            || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatefunction) {
301                        if (ast.tokencode == '(' || ast.tokencode == TBaseType.left_parenthesis_2) {
302                            ++lcNestedParens;
303                        }
304                        if (ast.tokencode == ')' && --lcNestedParens < 0) {
305                            lcNestedParens = 0;
306                        }
307                        if (ast.tokencode == TBaseType.rrw_begin) {
308                            ++lcNestedBeginEnd;
309                        }
310                        if (ast.tokencode == TBaseType.rrw_end) {
311                            boolean countThisEnd = true;
312                            if (lcNestedSample > 0) {
313                                // don't count sample...end
314                                --lcNestedSample;
315                                countThisEnd = false;
316                            } else if (lcNestedCase > 0) {
317                                // don't count case...end
318                                --lcNestedCase;
319                                countThisEnd = false;
320                            }
321                            if (countThisEnd) {
322                                // don't count end while/if/case/loop/for/repeat
323                                TSourceToken nt = ast.nextSolidToken();
324                                if (nt != null) {
325                                    countThisEnd = nt.tokencode != TBaseType.rrw_while
326                                            && nt.tokencode != TBaseType.rrw_if
327                                            && nt.tokencode != TBaseType.rrw_case
328                                            && nt.tokencode != TBaseType.rrw_loop
329                                            && nt.tokencode != TBaseType.rrw_for
330                                            && nt.tokencode != TBaseType.rrw_repeat;
331                                }
332                            }
333                            if (countThisEnd && --lcNestedBeginEnd < 0) {
334                                lcNestedBeginEnd = 0;
335                            }
336                        }
337                        readyToEnd = lcNestedParens == 0 && lcNestedBeginEnd == 0;
338                    }
339                    // single stmt in function/procedure/trigger may use ; as terminate char
340                    // so default terminate char is ;, if begin is found, then
341                    // set terminate char to DelimiterChar
342                    if (this.curdelimiterchar != this.delimiterChar) {
343                        if (ast.tokencode == TBaseType.rrw_begin) {
344                            this.curdelimiterchar = this.delimiterChar;
345                        } else if (ast.tokencode == TBaseType.mslabel && ast.container.nextsolidtoken(ast, 1, false).tokencode == TBaseType.rrw_begin) {
346                            this.curdelimiterchar = this.delimiterChar;
347                        }
348                    }
349                    if (this.curdelimiterchar == ';') {
350                        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
351                        if (ast.tokentype == ETokenType.ttsemicolon
352                                && (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger
353                                    || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure
354                                    || this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatefunction)
355                                && readyToEnd) {
356                            gst = EFindSqlStateType.stnormal;
357                            this.gcurrentsqlstatement.semicolonended = ast;
358                            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
359                            continue block10;
360                        }
361                    } else {
362                        if (ast.getAstext().length() == 1) {
363                            ch = ast.getAstext().charAt(0);
364                        } else if (ast.getAstext().length() > 1) {
365                            ch = ast.getAstext().charAt(ast.getAstext().length() - 1);
366                        } else {
367                            ch = ' ';
368                        }
369                        if (ast.tokencode != TBaseType.cmtslashstar && ast.tokencode != TBaseType.cmtdoublehyphen
370                                && ch == this.curdelimiterchar && ast.isFirstTokenOfLine() && readyToEnd) {
371                            if (ast.getAstext().length() > 1) {
372                                lcstr = ast.getAstext().substring(0, ast.getAstext().length() - 1);
373                                int c2 = this.flexer.getkeywordvalue(lcstr);
374                                if (c2 > 0) {
375                                    ast.tokencode = c2;
376                                }
377                            } else {
378                                this.gcurrentsqlstatement.semicolonended = ast;
379                            }
380                            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
381                            gst = EFindSqlStateType.stnormal;
382                            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
383                        } else if (ast.searchToken(TBaseType.rrw_create, 1) != null && ast.searchToken(TBaseType.rrw_procedure, 4) != null && readyToEnd) {
384                            gst = EFindSqlStateType.stnormal;
385                            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
386                        } else if (ast.tokencode == ';') {
387                            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
388                            boolean startNewStatement = false;
389                            if (readyToEnd) {
390                                startNewStatement = this.sqlcmds.issql(ast.nextSolidToken(), gst, this.gcurrentsqlstatement) != null;
391                            }
392                            if (startNewStatement) {
393                                gst = EFindSqlStateType.stnormal;
394                                this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
395                            }
396                        } else {
397                            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
398                        }
399                    }
400                    if (gst == EFindSqlStateType.ststoredprocedure && ast.tokencode == TBaseType.cmtdoublehyphen && ast.toString().trim().endsWith(TBaseType.sqlflow_stmt_delimiter_str)) {
401                        gst = EFindSqlStateType.stnormal;
402                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
403                    }
404                }
405            }
406        }
407        if (TBaseType.assigned(this.gcurrentsqlstatement) && (gst == EFindSqlStateType.stsql || gst == EFindSqlStateType.stBTEQCmd || gst == EFindSqlStateType.ststoredprocedure || gst == EFindSqlStateType.sterror
408                || gst == EFindSqlStateType.stFastExportCmd || gst == EFindSqlStateType.stFastLoadCmd || gst == EFindSqlStateType.stMultiLoadCmd)) {
409            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, true, builder);
410        }
411        builder.sqlStatements(this.sqlstatements);
412        builder.syntaxErrors(this.syntaxErrors instanceof ArrayList ? (ArrayList)this.syntaxErrors : new ArrayList(this.syntaxErrors));
413        builder.errorCode(this.syntaxErrors.isEmpty() ? 0 : this.syntaxErrors.size());
414        return errorcount;
415    }
416
417    private void handleLeftParenthesisToken(TSourceToken ast, int i) {
418        TSourceToken lcnextst = this.sourcetokenlist.nextsolidtoken(i, 1, false);
419        TSourceToken lcprevst = this.getprevsolidtoken(ast);
420        if ((lcprevst != null) && (
421                (lcprevst.tokencode == TBaseType.rrw_teradata_cast)
422                || (lcprevst.tokencode == TBaseType.rrw_year) // sample sql: select year(date) - 1 mantisbt/view.php?id=3049
423        )) {
424            lcnextst = null; // don't check next token if prev token is cast. sample: cast(DATETIME as BIGINT)
425        }
426        if ((lcprevst != null) && (TFunctionCall.isBuiltIn(lcprevst.toString(), EDbVendor.dbvteradata))) {
427            if (((lcprevst.tokencode == TBaseType.rrw_end)
428                    || (lcprevst.tokencode == TBaseType.rrw_time)
429                    || (lcprevst.tokencode == TBaseType.rrw_date)
430                    || (lcprevst.tokencode == TBaseType.rrw_timestamp)
431                    || (lcprevst.tokencode == TBaseType.rrw_teradata_type)
432                    || (lcprevst.toString().equalsIgnoreCase("current_date"))
433                    || (lcprevst.toString().equalsIgnoreCase("current_time"))
434                    || (lcprevst.toString().equalsIgnoreCase("current_timestamp"))
435                    || (lcprevst.toString().equalsIgnoreCase("user"))
436                    || (lcprevst.toString().equalsIgnoreCase("current_user"))
437            )) {
438                // Keep lcnextst as is
439            } else {
440                lcnextst = null;
441            }
442        }
443        if (lcnextst != null) {
444            EDataType dataType = TTypeName.searchTeradataTypeByName(lcnextst.toString());
445            if ((dataType != null) && (lcprevst != null) && (lcprevst.tokencode != TBaseType.rrw_teradata_period)) {
446                ast.tokencode = TBaseType.rrw_teradata_start_data_conversion;
447                // lcnextst2 is the token 2 steps after (
448                TSourceToken lcnextst2 = this.sourcetokenlist.nextsolidtoken(i, 2, false);
449                // lcnextst3 is the token 3 steps after (
450                TSourceToken lcnextst3 = this.sourcetokenlist.nextsolidtoken(i, 3, false);
451                if (lcnextst2.tokencode == ',') {
452                    // this is data conversion: (date,format '$xxx'),
453                    // this is not data conversion: trunc(date,'yy')
454                    TSourceToken st1 = lcnextst3;
455                    TSourceToken st2 = this.sourcetokenlist.nextsolidtoken(i, 4, false);
456                    if (TDatatypeAttribute.searchDataTypeAttributeByName(st1, st2) == null) {
457                        // this is not data conversion, such as :trunc(date,'yy')
458                        ast.tokencode = '(';
459                    }
460                } else if (lcnextst2 != null) {
461                    // lcnextst3 may be null when (date) is at end of query
462                    this.handleDataTypeConversion(ast, dataType, lcnextst2, lcnextst3, lcprevst);
463                }
464            } else {
465                TSourceToken st1 = lcnextst;
466                TSourceToken st2 = this.sourcetokenlist.nextsolidtoken(i, 2, false);
467                if (TDatatypeAttribute.searchDataTypeAttributeByName(st1, st2) != null) {
468                    ast.tokencode = TBaseType.rrw_teradata_start_data_conversion;
469                }
470            }
471        }
472    }
473
474    private void handleDataTypeConversion(TSourceToken ast, EDataType dataType, TSourceToken lcnextst2, TSourceToken lcnextst3, TSourceToken lcprevst) {
475        switch (dataType) {
476            case date_t:
477            case time_t:
478                // next token is: ) or comma or data attribute token
479                if ((lcnextst2.tokencode == ')')
480                        || ((lcnextst2.tokencode == '(') && (lcnextst3 != null) && (lcnextst3.tokencode == TBaseType.iconst))) { // hire_date (time(6))
481                    // ident(DATE) / ident(TIME): keep the data-conversion reading.
482                    // Teradata documents column_name(data_type) as CAST shorthand, and
483                    // whitespace before '(' is not significant, so col1(DATE) and
484                    // col1 (DATE) must produce the same parse tree (Mantis 4647 - the
485                    // earlier whitespace-based disambiguation from Mantis 4023 silently
486                    // corrupted JOIN predicates like o.order_ts(DATE)).
487                    // The genuinely ambiguous case - a KNOWN function taking the bare
488                    // DATE/TIME literal, e.g. TD_WEDNESDAY(date) - never reaches this
489                    // branch: built-in function names (including the TD_* day functions
490                    // in teradata_function.properties) are filtered by the
491                    // TFunctionCall.isBuiltIn check above, which leaves their '('
492                    // untouched so they parse as function calls.
493                } else {
494                    ast.tokencode = '(';
495                }
496                break;
497            case timestamp_t:
498            case interval_t:
499                if (lcnextst2.tokencode == TBaseType.sconst) { // INSERT t1 (TIME '10:44:25.123-08:00',TIMESTAMP '2000-09-20 10:44:25.1234')
500                    ast.tokencode = '(';
501                }
502                break;
503            case period_t:
504                if (lcnextst2.tokencode == '(') {
505                    // 只有 period() 才确保这里 period 是datatype,否则period不是datatype
506                } else {
507                    ast.tokencode = '(';
508                }
509                break;
510            case char_t:
511                if (lcnextst2.tokencode == ')') {
512                    // NULL (CHAR) AS B
513                } 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')
514                    ast.tokencode = '(';
515                }
516                break;
517            default:
518                // A cast-shorthand like (NUMBER(10,2)) always carries an integer
519                // constant as its first precision argument. When the token after the
520                // type-name's '(' is anything else — e.g. a macro/procedure parameter
521                // named like a type: CREATE MACRO m (number (INTEGER)) — this is not
522                // a data conversion, so restore the plain parenthesis.
523                if ((lcnextst2 != null) && (lcnextst2.tokencode == '(')
524                        && ((lcnextst3 == null) || (lcnextst3.tokencode != TBaseType.iconst))) {
525                    ast.tokencode = '(';
526                }
527                break;
528        }
529    }
530
531    private void handleForToken(TSourceToken ast) {
532        TSourceToken nextToken = ast.searchTokenAfterObjectName();
533        if (nextToken != null) {
534            if (nextToken.tokencode == TBaseType.rrw_as) {
535                ast.tokencode = TBaseType.rrw_teradata_for_loop;
536            }
537        }
538    }
539
540    private void handleLastToken(TSourceToken ast) {
541        TSourceToken nextToken = ast.searchToken('(', 1);
542        if (nextToken != null) {
543            ast.tokencode = TBaseType.rrw_last_function;
544        }
545    }
546
547    private void handlePivotToken(TSourceToken ast) {
548        TSourceToken nextToken = ast.searchToken('(', 1);
549        if (nextToken == null) {
550            ast.tokencode = TBaseType.ident;
551        }
552    }
553
554    private void handlePeriodToken(TSourceToken ast) {
555        TSourceToken nextToken = ast.nextSolidToken();
556        if (nextToken == null) {
557            ast.tokencode = TBaseType.ident;
558        } else if (nextToken.tokencode == '(') {
559            // keep it as keyword
560        } else if (nextToken.tokencode == TBaseType.rrw_for) {
561            // keep it as keyword
562        } else if (nextToken.tokencode == TBaseType.sconst) {
563            // sample: DEFAULT PERIOD '(2005-02-03, 2006-02-03)'
564            // keep it as keyword
565        } else {
566            if (ast.prevSolidToken() == null) {
567                ast.tokencode = TBaseType.ident;
568            } else {
569                if (!ast.prevSolidToken().toString().equalsIgnoreCase("anchor")) {
570                    ast.tokencode = TBaseType.ident;
571                }
572            }
573        }
574    }
575
576    private void handleTransactionToken(TSourceToken ast) {
577        TSourceToken lcprevst = this.getprevsolidtoken(ast);
578        if (lcprevst != null) {
579            if (lcprevst.tokencode == TBaseType.rrw_end) {
580                lcprevst.tokencode = TBaseType.rrw_teradata_end_t;
581            }
582        }
583    }
584
585    private void handleReplaceToken(TSourceToken ast) {
586        TSourceToken nextToken = ast.searchToken('(', 1);
587        if (nextToken != null) {
588            ast.tokencode = TBaseType.ident;
589        }
590    }
591
592    private void handleBetweenToken(TSourceToken ast) {
593        TSourceToken nextToken = ast.nextSolidToken();
594        if (nextToken != null) {
595            if ((nextToken.tokencode == TBaseType.rrw_begin) || (nextToken.tokencode == TBaseType.rrw_end)) {
596                // BETWEEN begin(DODD_EFF_PERI)
597                nextToken.tokencode = TBaseType.ident;
598            }
599        }
600    }
601
602    private void handleDeclareToken(TSourceToken ast) {
603        TSourceToken nextToken = ast.nextSolidToken();
604        if (nextToken != null) {
605            TSourceToken nextnextToken = nextToken.nextSolidToken();
606            if (nextnextToken != null) {
607                if ((nextnextToken.toString().equalsIgnoreCase("cursor"))
608                        || (nextnextToken.toString().equalsIgnoreCase("insensitive"))
609                        || (nextnextToken.toString().equalsIgnoreCase("scroll"))
610                        || (nextnextToken.toString().equalsIgnoreCase("no"))) {
611                    nextToken.tokencode = TBaseType.rrw_teradata_cursor_name;
612                } else if ((nextnextToken.toString().equalsIgnoreCase("condition"))) {
613                    nextToken.tokencode = TBaseType.rrw_teradata_condition_name;
614                }
615            }
616        }
617    }
618
619    private void handleCaseToken(TSourceToken ast) {
620        int lcNest = 1;
621        for (int m = ast.posinlist + 1; m < this.sourcetokenlist.size() - 1; m++) {
622            TSourceToken st = this.sourcetokenlist.get(m);
623            if (st.tokencode == TBaseType.rrw_case) {
624                st.tag = 1; // this is nested case keyword, don't check this token later
625                lcNest++;
626            } else if (st.tokencode == TBaseType.rrw_begin) {
627                // BEGIN...END blocks consume an END token, track them
628                lcNest++;
629            }
630            if (st.tokencode == TBaseType.rrw_end) {
631                TSourceToken nextSolid = st.nextSolidToken();
632                // END IF, END WHILE, END LOOP, END FOR, END REPEAT close their own blocks,
633                // not our CASE — skip them entirely
634                if (nextSolid != null && (nextSolid.tokencode == TBaseType.rrw_if
635                        || nextSolid.tokencode == TBaseType.rrw_while
636                        || nextSolid.tokencode == TBaseType.rrw_loop
637                        || nextSolid.tokencode == TBaseType.rrw_for
638                        || nextSolid.tokencode == TBaseType.rrw_repeat)) {
639                    continue;
640                }
641                lcNest--;
642                if (lcNest == 0) {
643                    if (nextSolid != null) {
644                        if (nextSolid.tokencode == TBaseType.rrw_case) {
645                            ast.tokencode = TBaseType.rrw_teradata_case_stmt;
646                            nextSolid.tag = 1; // this is case after end, don't check this token later
647                        }
648                    }
649                    // Always break after finding the matching END, whether it's followed by CASE or not
650                    // This prevents CASE expressions from incorrectly matching with later "END CASE" constructs
651                    break;
652                }
653            }
654        }
655    }
656
657    private void handleGrantToken(TSourceToken ast) {
658        TSourceToken prevSolidToken = ast.prevSolidToken();
659        if (prevSolidToken != null) {
660            if (prevSolidToken.tokencode == TBaseType.rrw_with) {
661                prevSolidToken.tokencode = TBaseType.rrw_teradata_with_grant;
662            }
663        }
664    }
665
666    private void handleVariableToken(TSourceToken ast) {
667        // USING ( _spVV8 VARCHAR(1024) CHARACTER SET UNICODE ) SELECT INTO :_spVV8 ;
668        // variable after into keyword treat as identifier
669        TSourceToken prevSolidToken = ast.prevSolidToken();
670        if (prevSolidToken != null) {
671            if (prevSolidToken.tokencode == TBaseType.rrw_into) {
672                ast.tokencode = TBaseType.ident;
673            }
674        }
675    }
676
677    private void handleSequencedToken(TSourceToken ast) {
678        TSourceToken nextSolidToken = ast.nextSolidToken();
679        if (nextSolidToken != null) {
680            if ((nextSolidToken.tokencode == TBaseType.rrw_teradata_validtime)) {
681                TSourceToken nextToken2 = nextSolidToken.nextSolidToken();
682                if (nextToken2 != null) {
683                    if ((nextToken2.tokencode == TBaseType.rrw_select)
684                            || (nextToken2.tokencode == TBaseType.rrw_insert)
685                            || (nextToken2.tokencode == TBaseType.rrw_update)
686                            || (nextToken2.tokencode == TBaseType.rrw_delete)
687                            || (nextToken2.tokencode == TBaseType.rrw_merge)) {
688                        // SEQUENCED VALIDTIME select/insert/update/delete/merge
689                        ast.tokencode = TBaseType.lexspace;
690                        nextSolidToken.tokencode = TBaseType.lexspace;
691                    }
692                }
693            }
694        }
695    }
696
697    private EFindSqlStateType handleMultiLoadCmdState(TSourceToken ast, EFindSqlStateType gst, SqlParseResult.Builder builder) {
698        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
699        if ((ast.tokencode == ';') && (ast.isLastTokenOfLine())) {
700            TSourceToken nextToken = ast.nextSolidToken();
701            TSourceToken nextToken2 = nextToken != null ? nextToken.nextSolidToken() : null;
702            if (nextToken != null && nextToken2 != null &&
703                    nextToken.tokencode == '.' &&
704                    nextToken2.toString().equalsIgnoreCase("FIELD") &&
705                    nextToken.isFirstTokenOfLine()) {
706                // Remain in stMultiLoadCmd state
707            } else {
708                this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
709                gst = EFindSqlStateType.stnormal;
710            }
711        } else if ((ast.tokencode == ';') && this.isSqlStyleSetMultiLoadCmd()) {
712            // A SQL-style `SET` directive (e.g. SET QUERY_BAND = '...' FOR SESSION)
713            // is terminated by its own ';' even when it is NOT the last token of
714            // the line. Without this, a statement that follows the ';' on the same
715            // line (SET QUERY_BAND = 'A=1'; SELECT c1 FROM t1;) is swallowed into
716            // the SET directive and never reaches downstream processing (MantisBT
717            // 4584). The ';' inside a band-specification literal is part of an
718            // SCONST token, not a ';' token, so it never triggers this branch.
719            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
720            gst = EFindSqlStateType.stnormal;
721        } else if (ast.isLastTokenOfLine()) {
722            // .IF ERRORCODE <> 0 THEN
723            // INSERT INTO Sou_EMP_Tab
724            //        (1, 'bala');
725            String cmdstr = this.gcurrentsqlstatement.sourcetokenlist.get(0).toString();
726            if (this.gcurrentsqlstatement.sourcetokenlist.size() > 1) {
727                cmdstr = cmdstr + this.gcurrentsqlstatement.sourcetokenlist.get(1).toString();
728            }
729            if ((cmdstr.toLowerCase().startsWith(".if")) || (cmdstr.toLowerCase().startsWith(".set")) || (cmdstr.toLowerCase().startsWith("set"))) {
730                // A SQL-style SET directive (SET QUERY_BAND = '...') may continue on the
731                // next line with FOR SESSION / FOR TRANSACTION and is terminated by its
732                // own ';'. Do not cut it at end-of-line when the next solid token is FOR.
733                TSourceToken continuation = ast.nextSolidToken();
734                boolean sqlSetContinues = this.isSqlStyleSetMultiLoadCmd()
735                        && continuation != null && continuation.tokencode == TBaseType.rrw_for;
736                if (!sqlSetContinues) {
737                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
738                    gst = EFindSqlStateType.stnormal;
739                }
740            }
741        }
742        return gst;
743    }
744
745    /**
746     * True when the MultiLoad-state command currently being collected is a
747     * SQL-style {@code SET} directive — one whose first solid token is the bare
748     * {@code SET} keyword (for example {@code SET QUERY_BAND = '...' FOR SESSION}).
749     * <p>
750     * These directives are session-level SQL statements that terminate at ';',
751     * unlike genuine MultiLoad utility directives which are dot-prefixed
752     * ({@code .SET}, {@code .FIELD}, ...) and are line-based. Restricting the
753     * mid-line ';' termination to bare {@code SET} keeps real dot-commands
754     * untouched.
755     */
756    private boolean isSqlStyleSetMultiLoadCmd() {
757        if (this.gcurrentsqlstatement == null) return false;
758        for (int i = 0; i < this.gcurrentsqlstatement.sourcetokenlist.size(); i++) {
759            TSourceToken t = this.gcurrentsqlstatement.sourcetokenlist.get(i);
760            if (!t.issolidtoken()) continue;
761            return t.tokencode == TBaseType.rrw_set;
762        }
763        return false;
764    }
765
766    private EFindSqlStateType handleFastExportCmdState(TSourceToken ast, EFindSqlStateType gst, SqlParseResult.Builder builder) {
767        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
768        if ((ast.tokencode == ';') && (ast.isLastTokenOfLine())) {
769            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
770            gst = EFindSqlStateType.stnormal;
771        } else {
772            boolean findSQL = false;
773            TSourceToken nextst = ast.nextToken();
774            if (nextst != null) {
775                if (nextst.tokentype == ETokenType.ttreturn) {
776                    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                }
782            }
783        }
784        return gst;
785    }
786
787    private EFindSqlStateType handleFastLoadCmdState(TSourceToken ast, EFindSqlStateType gst, SqlParseResult.Builder builder) {
788        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
789        if ((ast.tokencode == ';') && (ast.isLastTokenOfLine())) {
790            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
791            gst = EFindSqlStateType.stnormal;
792        } else if (ast.tokencode == TBaseType.lexnewline) {
793            TSourceToken nextst = ast.nextSolidToken();
794            if (nextst != null) {
795                if (nextst.tokencode == TBaseType.BTEQCMD) {
796                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
797                    gst = EFindSqlStateType.stFastLoadCmd;
798                } else if (nextst.tokencode == '.') {
799                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
800                    gst = EFindSqlStateType.stFastLoadCmd;
801                } else if (this.sqlcmds.issql(nextst, gst, this.gcurrentsqlstatement) != null) {
802                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
803                    gst = EFindSqlStateType.stnormal;
804                }
805            }
806        }
807        return gst;
808    }
809
810    private EFindSqlStateType handleBTEQCmdState(TSourceToken ast, SqlParseResult.Builder builder) {
811        EFindSqlStateType gst = EFindSqlStateType.stBTEQCmd;
812        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
813
814        if (ast.tokencode == TBaseType.lexnewline) {
815            if (this.isContinueBTEQCmd) {
816                this.isContinueBTEQCmd = false;
817            } else if (this.isInIfThenStatement) {
818                // Continue collecting tokens for .IF/.ELSEIF THEN until semicolon
819                // Don't complete the statement on newline
820            } else {
821                if (((TTeradataBTEQCmd)this.gcurrentsqlstatement).getBteqCmdType() == BteqCmdType.LOGON) {
822                    boolean findSQL = (this.sqlcmds.issql(ast.nextSolidToken(), gst, this.gcurrentsqlstatement) != null);
823                    if (findSQL) {
824                        this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
825                        gst = EFindSqlStateType.stnormal;
826                    }
827                } else {
828                    this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
829                    gst = EFindSqlStateType.stnormal;
830                }
831            }
832        } else if ((ast.tokencode == '-') && (ast.isLastTokenOfLine())) {
833            if (!this.inBTEQComment) {
834                this.isContinueBTEQCmd = true;
835            }
836        } else if (ast.tokencode == TBaseType.lexspace) {
837            // Do nothing
838        } else if (ast.tokencode == ';') {
839            // Reset IF THEN state when statement ends
840            this.isInIfThenStatement = false;
841            this.onRawStatementComplete(this.parserContext, this.gcurrentsqlstatement, this.fparser, null, this.sqlstatements, false, builder);
842            boolean findNewCmdInSameLine = this.checkForNewBTEQCmdInSameLine(ast);
843            if (findNewCmdInSameLine) {
844                gst = EFindSqlStateType.stBTEQCmd;
845            } else {
846                gst = EFindSqlStateType.stnormal;
847            }
848        } else if (ast.tokencode == '*') {
849            this.isContinueBTEQCmd = false;
850        } else { // solid token
851            // Check if this is a THEN keyword at end of line for .IF/.ELSEIF commands
852            // In this case, the BTEQ command should continue to include the following SQL statement
853            BteqCmdType cmdType = ((TTeradataBTEQCmd)this.gcurrentsqlstatement).getBteqCmdType();
854            if ((cmdType == BteqCmdType.IF || cmdType == BteqCmdType.ELSEIF)
855                && ast.tokencode == TBaseType.rrw_then
856                && ast.isLastTokenOfLine()) {
857                this.isContinueBTEQCmd = true;
858                this.isInIfThenStatement = true;
859            } else {
860                this.isContinueBTEQCmd = false;
861            }
862        }
863        return gst;
864    }
865
866    private boolean checkForNewBTEQCmdInSameLine(TSourceToken ast) {
867        boolean findNewCmdInSameLine = false;
868        if (!ast.isLastTokenOfLine()) {
869            // check if next cmd like below:
870            // .SET SIDETITLES ON; .SET FOLDLINE ON ALL
871            TSourceToken nextst = ast.nextSolidToken();
872            if ((nextst != null) && (nextst.tokencode == '.')) {
873                TSourceToken nextst2 = nextst.nextSolidToken();
874                TSourceToken searchToken = null;
875                if (nextst2 != null) {
876                    if (nextst2.tokencode == TBaseType.rrw_set) {
877                        TSourceToken nextst3 = nextst2.nextSolidToken();
878                        if (nextst3 != null) {
879                            searchToken = nextst3;
880                        }
881                    } else {
882                        searchToken = nextst2;
883                    }
884
885                    BteqCmdType bteqCmdType = BteqCmdType.searchBteqCmd(searchToken);
886                    if (bteqCmdType != null) {
887                        this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
888                        ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setBteqCmdType(bteqCmdType);
889                        this.gcurrentsqlstatement.sourcetokenlist.add(ast);
890                        findNewCmdInSameLine = true;
891                    }
892                }
893            }
894        }
895        return findNewCmdInSameLine;
896    }
897
898    private EFindSqlStateType handleNormalState(TSourceToken ast, EFindSqlStateType gst, TSourceToken lcprevsolidtoken, SqlParseResult.Builder builder) {
899        if ((ast.tokencode == TBaseType.cmtdoublehyphen)
900                || (ast.tokencode == TBaseType.cmtslashstar)
901                || (ast.tokencode == TBaseType.lexspace)
902                || (ast.tokencode == TBaseType.lexnewline)
903                || (ast.tokentype == ETokenType.ttsemicolon)) {
904            if (TBaseType.assigned(this.gcurrentsqlstatement)) {
905                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
906            }
907
908            if ((lcprevsolidtoken != null) && (ast.tokentype == ETokenType.ttsemicolon)) {
909                if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) {
910                    // ;;;; continuous semicolon, treat it as comment
911                    ast.tokentype = ETokenType.ttsimplecomment;
912                    ast.tokencode = TBaseType.cmtdoublehyphen;
913                }
914            }
915
916            return gst;
917        }
918        if ((gst = this.checkForBTEQOrUtilityCommand(ast, gst)) != EFindSqlStateType.stnormal) {
919            return gst;
920        }
921
922        // find a tokentext to start sql or stored procedure mode
923        this.gcurrentsqlstatement = this.sqlcmds.issql(ast, gst, this.gcurrentsqlstatement);
924
925        if (TBaseType.assigned(this.gcurrentsqlstatement)) {
926            ESqlStatementType[] ses = {ESqlStatementType.sstcreateprocedure, ESqlStatementType.sstteradatacreatefunction, ESqlStatementType.sstcreatetrigger};
927            if (this.includesqlstatementtype(this.gcurrentsqlstatement.sqlstatementtype, ses)) {
928                gst = EFindSqlStateType.ststoredprocedure;
929                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
930                if ((this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreateprocedure)
931                        || (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstteradatacreatefunction)
932                        || (this.gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstcreatetrigger)) {
933                    this.curdelimiterchar = ';';
934                }
935            } else {
936                gst = EFindSqlStateType.stsql;
937                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
938            }
939        }
940
941        if ((!TBaseType.assigned(this.gcurrentsqlstatement)) && (ast.isFirstTokenOfLine())) {
942            gst = this.flexer.cmdType(ast);
943            if ((gst == EFindSqlStateType.stMultiLoadCmd) || (gst == EFindSqlStateType.stFastLoadCmd) || (gst == EFindSqlStateType.stFastExportCmd)) {
944                // SQL-style `SET <variable> = <expression>` is a procedural assignment that
945                // ends with ';' and may span multiple lines. MultiLoad/BTEQ SET directives do
946                // not use '=' — if '=' follows the identifier we are looking at a SQL SET.
947                if (ast.tokencode == TBaseType.rrw_set && isSqlStyleSetAssignment(ast)) {
948                    this.gcurrentsqlstatement = new TSetStmt(this.vendor);
949                    this.gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstset;
950                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
951                    gst = EFindSqlStateType.stsql;
952                    return gst;
953                } else {
954                    this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
955                    ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
956                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
957                    return gst;
958                }
959            } else {
960                gst = EFindSqlStateType.stnormal;
961            }
962        }
963
964        if (!TBaseType.assigned(this.gcurrentsqlstatement)) { // error tokentext found
965            this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo),
966                    "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist));
967
968            ast.tokentype = ETokenType.tttokenlizererrortoken;
969            gst = EFindSqlStateType.sterror;
970
971            this.gcurrentsqlstatement = new TUnknownSqlStatement(this.vendor);
972            this.gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid;
973            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
974        }
975        return gst;
976    }
977
978    /**
979     * SQL-style procedural SET variable assignment where the right-hand side
980     * is a parenthesized expression/subquery: {@code SET <name> = (<expr>)}.
981     * This is distinct from Teradata BTEQ/MultiLoad SET directives (which use
982     * space-separated values, no '=', terminate at end-of-line) and from
983     * SET statements whose tail is vendor-specific BTEQ syntax (for example
984     * {@code SET QUERY_BAND = '...' UPDATE FOR SESSION}).
985     * <p>
986     * Restricting the detection to RHS starting with '(' keeps the fix tightly
987     * scoped to the reported failure (MantisBT 3336) — a subquery-driven
988     * assignment that legitimately spans multiple lines and terminates at ';'.
989     */
990    private boolean isSqlStyleSetAssignment(TSourceToken setToken) {
991        TSourceToken next = setToken.nextSolidToken();
992        if (next == null) return false;
993        TSourceToken afterNext = next.nextSolidToken();
994        if (afterNext == null || afterNext.tokencode != '=') return false;
995        TSourceToken rhs = afterNext.nextSolidToken();
996        return rhs != null && rhs.tokencode == '(';
997    }
998
999    private EFindSqlStateType checkForBTEQOrUtilityCommand(TSourceToken ast, EFindSqlStateType gst) {
1000        // Check for .bteq commands: either "." at start of line, or "." after ";" at start of line
1001        boolean isDotAtStartOfLine = (ast.tokencode == '.') && (ast.isFirstTokenOfLine());
1002        boolean isDotAfterSemicolonAtStartOfLine = false;
1003        if ((ast.tokencode == '.') && !isDotAtStartOfLine) {
1004            TSourceToken prevToken = ast.prevSolidToken();
1005            if ((prevToken != null) && (prevToken.tokencode == ';') && (prevToken.isFirstTokenOfLine())) {
1006                isDotAfterSemicolonAtStartOfLine = true;
1007            }
1008        }
1009
1010        if (isDotAtStartOfLine || isDotAfterSemicolonAtStartOfLine) {
1011            // .bteq commands
1012            TSourceToken nextst = ast.nextSolidToken();
1013            if (nextst != null) {
1014                gst = this.flexer.cmdType(nextst);
1015                boolean foundCmd = false;
1016                switch (gst) {
1017                    case stMultiLoadCmd:
1018                        this.gcurrentsqlstatement = new TTeradataMultiLoadCmd(this.vendor);
1019                        gst = EFindSqlStateType.stMultiLoadCmd;
1020                        ((TTeradataMultiLoadCmd)this.gcurrentsqlstatement).setCmdType(TTeradataHelper.searchMultiLoadTypeByName(nextst.getAstext()));
1021                        foundCmd = true;
1022                        break;
1023                    case stFastExportCmd:
1024                        this.gcurrentsqlstatement = new TTeradataFastExportCmd(this.vendor);
1025                        gst = EFindSqlStateType.stFastExportCmd;
1026                        foundCmd = true;
1027                        break;
1028                    case stFastLoadCmd:
1029                        this.gcurrentsqlstatement = new TTeradataFastLoadCmd(this.vendor);
1030                        gst = EFindSqlStateType.stFastLoadCmd;
1031                        foundCmd = true;
1032                        break;
1033                    case stBTEQCmd:
1034                        this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1035                        ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setBteqCmdType(BteqCmdType.searchBteqCmd(nextst));
1036                        gst = EFindSqlStateType.stBTEQCmd;
1037                        foundCmd = true;
1038                        break;
1039                    default:
1040                        break;
1041                }
1042                if (foundCmd) {
1043                    this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1044                    return gst;
1045                }
1046            }
1047        } else if ((ast.tokencode == '*') && (ast.isFirstTokenOfLine())) {
1048            // BTEQ comment: * comment
1049            gst = EFindSqlStateType.stBTEQCmd;
1050            this.inBTEQComment = true;  // Mark that we're in a BTEQ comment
1051            this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1052            ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setBteqCmdType(BteqCmdType.COMMENT);
1053            this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1054            return gst;
1055        } else if ((ast.tokencode == '=') && (ast.isFirstTokenOfLine())) {
1056            // BTEQ command starting with =
1057            TSourceToken st1 = ast.prevSolidToken();
1058            if ((st1 != null) && (st1.tokencode == ';')) {
1059                gst = EFindSqlStateType.stBTEQCmd;
1060                this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1061                ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1062                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1063                return gst;
1064            }
1065        } else if ((ast.isFirstTokenOfLine()) && (BteqCmdType.searchBteqCmd(ast) != null)) {
1066            TSourceToken st1 = ast.prevSolidToken();
1067            if ((st1 != null) && (st1.tokencode == ';')) {
1068                gst = EFindSqlStateType.stBTEQCmd;
1069                this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1070                ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1071                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1072                return gst;
1073            }
1074        } else if ((ast.tokencode == '&') && (ast.isFirstTokenOfLine())) {
1075            // Teradata macro/substitution variable: &VARIABLE_NAME at start of line
1076            // Treat it as a BTEQ-like command that ends at end of line
1077            TSourceToken nextst = ast.nextSolidToken();
1078            if (nextst != null && (nextst.tokencode == TBaseType.ident || nextst.tokentype == ETokenType.ttidentifier)) {
1079                gst = EFindSqlStateType.stBTEQCmd;
1080                this.gcurrentsqlstatement = new TTeradataBTEQCmd(this.vendor);
1081                ((TTeradataBTEQCmd)this.gcurrentsqlstatement).setFindSqlStateType(gst);
1082                this.gcurrentsqlstatement.sourcetokenlist.add(ast);
1083                return gst;
1084            }
1085        }
1086        return EFindSqlStateType.stnormal;
1087    }
1088
1089
1090    private boolean includesqlstatementtype(ESqlStatementType type, ESqlStatementType[] types) {
1091        for (ESqlStatementType t : types) {
1092            if (type != t) continue;
1093            return true;
1094        }
1095        return false;
1096    }
1097
1098    @Override
1099    protected TStatementList performParsing(ParserContext context, TCustomParser parser, TCustomParser secondaryParser, TSourceTokenList tokens, TStatementList rawStatements) {
1100        this.fparser = (TParserTeradata)parser;
1101        this.sourcetokenlist = tokens;
1102        this.parserContext = context;
1103        this.sqlstatements = rawStatements;
1104        if (this.sqlstatements == null) {
1105            this.sqlstatements = new TStatementList();
1106            return this.sqlstatements;
1107        }
1108        if (this.sqlcmds == null) {
1109            this.sqlcmds = SqlCmdsFactory.get(this.vendor);
1110        }
1111        this.fparser.sqlcmds = this.sqlcmds;
1112
1113        // Initialize global context for semantic analysis
1114        // CRITICAL: When delegated from TGSqlParser, use TGSqlParser's frameStack
1115        // so that variables set in SET statements can be found by EXECUTE statements
1116        if (context != null && context.getGsqlparser() != null) {
1117            gudusoft.gsqlparser.TGSqlParser gsqlparser = (gudusoft.gsqlparser.TGSqlParser) context.getGsqlparser();
1118            this.frameStack = gsqlparser.getFrameStack();
1119
1120            // CRITICAL: Set gsqlparser on the NodeFactory - matches TGSqlParser behavior
1121            // This is needed for proper AST node creation during parsing
1122            this.fparser.getNf().setGsqlParser(gsqlparser);
1123
1124            // Create global context if needed
1125            this.globalContext = new gudusoft.gsqlparser.compiler.TContext();
1126            this.sqlEnv = new gudusoft.gsqlparser.sqlenv.TSQLEnv(this.vendor) {
1127                @Override
1128                public void initSQLEnv() {
1129                }
1130            };
1131            this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements);
1132        } else {
1133            this.initializeGlobalContext();
1134        }
1135
1136        for (int i = 0; i < this.sqlstatements.size(); ++i) {
1137            TCustomSqlStatement stmt = this.sqlstatements.getRawSql(i);
1138            try {
1139                stmt.setFrameStack(this.frameStack);
1140                int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree());
1141                boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE;
1142                if (doRecover && (parseResult != 0 || stmt.getErrorCount() > 0)) {
1143                    this.handleCreateTableErrorRecovery(stmt);
1144                }
1145                if (parseResult == 0 && stmt.getErrorCount() <= 0) continue;
1146                this.copyErrorsFromStatement(stmt);
1147                continue;
1148            }
1149            catch (Exception ex) {
1150                ex.printStackTrace();
1151                this.handleStatementParsingException(stmt, i, ex);
1152            }
1153        }
1154        if (this.globalFrame != null) {
1155            this.globalFrame.popMeFromStack(this.frameStack);
1156        }
1157        return this.sqlstatements;
1158    }
1159
1160    private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) {
1161        if (!(stmt.sqlstatementtype != ESqlStatementType.sstcreatetable && stmt.sqlstatementtype != ESqlStatementType.sstcreateindex || TBaseType.c_createTableStrictParsing)) {
1162            int nested = 0;
1163            boolean isIgnore = false;
1164            boolean isFoundIgnoreToken = false;
1165            TSourceToken firstIgnoreToken = null;
1166            for (int k = 0; k < stmt.sourcetokenlist.size(); ++k) {
1167                TSourceToken st = stmt.sourcetokenlist.get(k);
1168                if (isIgnore) {
1169                    if (st.issolidtoken() && st.tokencode != 59) {
1170                        isFoundIgnoreToken = true;
1171                        if (firstIgnoreToken == null) {
1172                            firstIgnoreToken = st;
1173                        }
1174                    }
1175                    if (st.tokencode == 59) continue;
1176                    st.tokencode = 273;
1177                    continue;
1178                }
1179                if (st.tokencode == 41 && --nested == 0) {
1180                    isIgnore = true;
1181                }
1182                if (st.tokencode != 40) continue;
1183                ++nested;
1184            }
1185            if (isFoundIgnoreToken) {
1186                stmt.parsestatement(null, false, this.parserContext.isOnlyNeedRawParseTree());
1187            }
1188        }
1189    }
1190
1191    @Override
1192    protected void performSemanticAnalysis(ParserContext context, TStatementList statements) {
1193        if (!TBaseType.isEnableResolver()) {
1194            return;
1195        }
1196        if (!this.getSyntaxErrors().isEmpty()) {
1197            return;
1198        }
1199        TSQLResolver resolver = new TSQLResolver(this.globalContext, statements);
1200        resolver.resolve();
1201    }
1202
1203    @Override
1204    protected void performInterpreter(ParserContext context, TStatementList statements) {
1205    }
1206
1207    @Override
1208    public String toString() {
1209        return "TeradataSqlParser{vendor=" + (Object)((Object)this.vendor) + "}";
1210    }
1211}