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