001package gudusoft.gsqlparser.parser; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.TGSqlParser; 006import gudusoft.gsqlparser.TCustomLexer; 007import gudusoft.gsqlparser.TCustomParser; 008import gudusoft.gsqlparser.TCustomSqlStatement; 009import gudusoft.gsqlparser.TLexerMssql; 010import gudusoft.gsqlparser.TParserMssqlSql; 011import gudusoft.gsqlparser.TSourceToken; 012import gudusoft.gsqlparser.TSourceTokenList; 013import gudusoft.gsqlparser.TStatementList; 014import gudusoft.gsqlparser.TSyntaxError; 015import gudusoft.gsqlparser.EFindSqlStateType; 016import gudusoft.gsqlparser.ETokenType; 017import gudusoft.gsqlparser.ETokenStatus; 018import gudusoft.gsqlparser.ESqlStatementType; 019import gudusoft.gsqlparser.EErrorType; 020import gudusoft.gsqlparser.stmt.TUnknownSqlStatement; 021import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock; 022import gudusoft.gsqlparser.stmt.mssql.TMssqlExecute; 023import gudusoft.gsqlparser.sqlcmds.ISqlCmds; 024import gudusoft.gsqlparser.sqlcmds.SqlCmdsFactory; 025import gudusoft.gsqlparser.compiler.TContext; 026import gudusoft.gsqlparser.sqlenv.TSQLEnv; 027import gudusoft.gsqlparser.compiler.TGlobalScope; 028import gudusoft.gsqlparser.compiler.TFrame; 029import gudusoft.gsqlparser.resolver.TSQLResolver; 030import gudusoft.gsqlparser.resolver2.TSQLResolver2; 031import gudusoft.gsqlparser.resolver2.TSQLResolverConfig; 032import gudusoft.gsqlparser.TLog; 033import gudusoft.gsqlparser.compiler.TASTEvaluator; 034 035import java.io.BufferedReader; 036import java.util.ArrayList; 037import java.util.List; 038import java.util.Stack; 039 040import static gudusoft.gsqlparser.ESqlStatementType.sstmssqlreturn; 041 042/** 043 * Microsoft SQL Server database SQL parser implementation. 044 * 045 * <p>This parser handles SQL Server-specific SQL syntax including: 046 * <ul> 047 * <li>T-SQL blocks (stored procedures, functions, triggers)</li> 048 * <li>BEGIN/END blocks</li> 049 * <li>TRY/CATCH error handling</li> 050 * <li>GO batch separator</li> 051 * <li>SQL Server-specific DML/DDL (MERGE, OPENROWSET, etc.)</li> 052 * <li>Special token handling (LOCK TABLE, COPY INTO, etc.)</li> 053 * </ul> 054 * 055 * <p><b>Design Notes:</b> 056 * <ul> 057 * <li>Extends {@link AbstractSqlParser} using the template method pattern</li> 058 * <li>Uses {@link TLexerMssql} for tokenization</li> 059 * <li>Uses {@link TParserMssqlSql} for parsing</li> 060 * <li>Delimiter character: ';' for SQL statements</li> 061 * </ul> 062 * 063 * <p><b>Usage Example:</b> 064 * <pre> 065 * // Get SQL Server parser from factory 066 * SqlParser parser = SqlParserFactory.get(EDbVendor.dbvmssql); 067 * 068 * // Build context 069 * ParserContext context = new ParserContext.Builder(EDbVendor.dbvmssql) 070 * .sqlText("SELECT * FROM Employees WHERE DepartmentID = 10") 071 * .build(); 072 * 073 * // Parse 074 * SqlParseResult result = parser.parse(context); 075 * 076 * // Access statements 077 * TStatementList statements = result.getSqlStatements(); 078 * </pre> 079 * 080 * @see SqlParser 081 * @see AbstractSqlParser 082 * @see TLexerMssql 083 * @see TParserMssqlSql 084 * @since 3.2.0.0 085 */ 086public class MssqlSqlParser extends AbstractSqlParser { 087 088 /** 089 * Construct SQL Server SQL parser. 090 * <p> 091 * Configures the parser for SQL Server database with default delimiter (;). 092 * <p> 093 * Following the original TGSqlParser pattern, the lexer and parser are 094 * created once in the constructor and reused for all parsing operations. 095 */ 096 public MssqlSqlParser() { 097 super(EDbVendor.dbvmssql); 098 this.delimiterChar = ';'; 099 this.defaultDelimiterStr = ";"; 100 101 // Create lexer once - will be reused for all parsing operations 102 this.flexer = new TLexerMssql(); 103 this.flexer.delimiterchar = this.delimiterChar; 104 this.flexer.defaultDelimiterStr = this.defaultDelimiterStr; 105 106 // Set parent's lexer reference for shared tokenization logic 107 this.lexer = this.flexer; 108 109 // Create parser once - will be reused for all parsing operations 110 this.fparser = new TParserMssqlSql(null); 111 this.fparser.lexer = this.flexer; 112 } 113 114 // ========== Parser Components ========== 115 116 /** The SQL Server lexer used for tokenization */ 117 public TLexerMssql flexer; 118 119 /** SQL parser (for SQL Server statements) */ 120 private TParserMssqlSql fparser; 121 122 /** Current statement being built during extraction */ 123 private TCustomSqlStatement gcurrentsqlstatement; 124 125 // Note: Global context and frame stack fields inherited from AbstractSqlParser: 126 // - protected TContext globalContext 127 // - protected TSQLEnv sqlEnv 128 // - protected Stack<TFrame> frameStack 129 // - protected TFrame globalFrame 130 131 // ========== AbstractSqlParser Abstract Methods Implementation ========== 132 133 /** 134 * Return the SQL Server lexer instance. 135 */ 136 @Override 137 protected TCustomLexer getLexer(ParserContext context) { 138 return this.flexer; 139 } 140 141 /** 142 * Return the SQL Server SQL parser instance with updated token list. 143 */ 144 @Override 145 protected TCustomParser getParser(ParserContext context, TSourceTokenList tokens) { 146 this.fparser.sourcetokenlist = tokens; 147 return this.fparser; 148 } 149 150 /** 151 * Call MSSQL-specific tokenization logic. 152 * <p> 153 * Delegates to domssqlsqltexttotokenlist which handles SQL Server's 154 * specific keyword recognition, bracket identifiers, and token generation. 155 */ 156 @Override 157 protected void tokenizeVendorSql() { 158 domssqlsqltexttotokenlist(); 159 } 160 161 /** 162 * Setup MSSQL parser for raw statement extraction. 163 * <p> 164 * MSSQL uses a single parser, so we inject sqlcmds and update 165 * the token list for the main parser only. 166 */ 167 @Override 168 protected void setupVendorParsersForExtraction() { 169 // Inject sqlcmds into parser (required for make_stmt) 170 this.fparser.sqlcmds = this.sqlcmds; 171 172 // Update token list for parser 173 this.fparser.sourcetokenlist = this.sourcetokenlist; 174 } 175 176 /** 177 * Call MSSQL-specific raw statement extraction logic. 178 * <p> 179 * Delegates to domssqlgetrawsqlstatements which handles SQL Server's 180 * statement delimiters (semicolon and GO command). 181 */ 182 @Override 183 protected void extractVendorRawStatements(SqlParseResult.Builder builder) { 184 domssqlgetrawsqlstatements(builder); 185 } 186 187 /** 188 * Perform full parsing of statements with syntax checking. 189 * <p> 190 * This method orchestrates the parsing of all statements. 191 */ 192 @Override 193 protected TStatementList performParsing(ParserContext context, 194 TCustomParser parser, 195 TCustomParser secondaryParser, 196 TSourceTokenList tokens, 197 TStatementList rawStatements) { 198 // Store references 199 this.fparser = (TParserMssqlSql) parser; 200 this.sourcetokenlist = tokens; 201 this.parserContext = context; 202 203 // Use the raw statements passed from AbstractSqlParser.parse() 204 this.sqlstatements = rawStatements; 205 206 // Initialize statement parsing infrastructure. Use the context vendor 207 // so delegated dialects (e.g. dbvexasol) get their own cmds resolver. 208 this.sqlcmds = SqlCmdsFactory.get(context != null ? context.getVendor() : vendor); 209 210 // Inject sqlcmds into parser (required for make_stmt and other methods) 211 this.fparser.sqlcmds = this.sqlcmds; 212 213 // Initialize global context for semantic analysis 214 // CRITICAL: When delegated from TGSqlParser, use TGSqlParser's frameStack 215 // so that variables set in SET statements can be found by EXECUTE statements 216 if (context != null && context.getGsqlparser() != null) { 217 TGSqlParser gsqlparser = (TGSqlParser) context.getGsqlparser(); 218 this.frameStack = gsqlparser.getFrameStack(); 219 220 // CRITICAL: Set gsqlparser on the NodeFactory - matches TGSqlParser behavior 221 // This is needed for proper AST node creation during parsing 222 this.fparser.getNf().setGsqlParser(gsqlparser); 223 224 // Create global context if needed 225 this.globalContext = new TContext(); 226 this.sqlEnv = new TSQLEnv(this.vendor) { 227 @Override 228 public void initSQLEnv() { 229 } 230 }; 231 this.globalContext.setSqlEnv(this.sqlEnv, this.sqlstatements); 232 } else { 233 initializeGlobalContext(); 234 } 235 236 // Parse each statement with exception handling for robustness 237 for (int i = 0; i < sqlstatements.size(); i++) { 238 TCustomSqlStatement stmt = sqlstatements.getRawSql(i); 239 240 try { 241 stmt.setFrameStack(frameStack); 242 243 // Parse the statement 244 int parseResult = stmt.parsestatement(null, false, context.isOnlyNeedRawParseTree()); 245 246 // Handle error recovery for CREATE TABLE/INDEX 247 boolean doRecover = TBaseType.ENABLE_ERROR_RECOVER_IN_CREATE_TABLE; 248 if (doRecover && ((parseResult != 0) || (stmt.getErrorCount() > 0))) { 249 handleCreateTableErrorRecovery(stmt); 250 } 251 252 // Collect syntax errors 253 if ((parseResult != 0) || (stmt.getErrorCount() > 0)) { 254 copyErrorsFromStatement(stmt); 255 } 256 257 } catch (Exception ex) { 258 // Use inherited exception handler from AbstractSqlParser 259 // This provides consistent error handling across all database parsers 260 // ex.printStackTrace(); 261 handleStatementParsingException(stmt, i, ex); 262 continue; 263 } 264 } 265 266 // Clean up frame stack 267 if (globalFrame != null) { 268 globalFrame.popMeFromStack(frameStack); 269 } 270 271 return this.sqlstatements; 272 } 273 274 // Note: initializeGlobalContext() inherited from AbstractSqlParser 275 // Note: No override of afterStatementParsed() needed - default (no-op) is appropriate for MSSQL 276 277 /** 278 * Exasol pre-pass: the Exasol dialect shares this parser, and its extra 279 * syntax is keyed on synthetic tokens (TBaseType.rrw_exasol_*) that the 280 * MSSQL lexer never emits. When the requested dialect is dbvexasol, this 281 * pass rewrites the Exasol-only words to those token codes (and hides a 282 * few tokens the shared grammar cannot carry, e.g. OR REPLACE after 283 * CREATE). Real T-SQL input never reaches the Exasol grammar rules. 284 */ 285 @Override 286 protected void processTokensBeforeParse(ParserContext context, TSourceTokenList tokens) { 287 super.processTokensBeforeParse(context, tokens); 288 if (context != null && context.getVendor() == EDbVendor.dbvmssql 289 && context.isMssqlColonBindVariablesEnabled()) { 290 mergeMssqlColonBindVariables(tokens); 291 } 292 if (context == null || context.getVendor() != EDbVendor.dbvexasol) { 293 return; 294 } 295 boolean stmtHasInvalid = false; 296 TSourceToken stmtFirst = null; 297 TSourceToken prevSolid = null; 298 for (int i = 0; i < tokens.size(); i++) { 299 TSourceToken st = tokens.get(i); 300 if (st.tokentype == ETokenType.ttwhitespace || st.tokentype == ETokenType.ttreturn 301 || st.tokentype == ETokenType.ttsimplecomment || st.tokentype == ETokenType.ttbracketedcomment) { 302 continue; 303 } 304 if (st.tokentype == ETokenType.ttsemicolon) { 305 stmtHasInvalid = false; 306 stmtFirst = null; 307 prevSolid = null; 308 continue; 309 } 310 if (stmtFirst == null) { 311 stmtFirst = st; 312 } 313 TSourceToken next = st.nextSolidToken(); 314 if (st.tokentype == ETokenType.ttidentifier || st.tokentype == ETokenType.ttkeyword) { 315 // CREATE OR REPLACE <obj>: hide OR REPLACE (T-SQL grammar has 316 // CREATE [OR ALTER] only; Exasol semantics identical for parse). 317 if (st.tokencode == TBaseType.rrw_or && prevSolid == stmtFirst 318 && wordIs(stmtFirst, "create") && wordIs(next, "replace")) { 319 hideToken(st); 320 hideToken(next); 321 continue; 322 } 323 // (EXPLAIN VIRTUAL <select>) as a derived table: hide the 324 // EXPLAIN VIRTUAL introducer so the inner select parses as a 325 // plain subquery. 326 if (wordIs(st, "explain") 327 && prevSolid != null && prevSolid.tokentype == ETokenType.ttleftparenthesis 328 && wordIs(next, "virtual")) { 329 hideToken(st); 330 hideToken(next); 331 continue; 332 } 333 if (wordIs(st, "qualify") && st.tokentype == ETokenType.ttidentifier) { 334 st.tokencode = TBaseType.rrw_exasol_qualify; 335 } else if (wordIs(st, "limit") && st.tokentype == ETokenType.ttidentifier) { 336 st.tokencode = TBaseType.rrw_mssql_limit; 337 } else if (wordIs(st, "invalid") && st.tokentype == ETokenType.ttidentifier 338 && startsVerificationKind(next)) { 339 st.tokencode = TBaseType.rrw_exasol_invalid; 340 stmtHasInvalid = true; 341 // hide an optional preceding WITH (SELECT cols WITH INVALID ...) 342 if (prevSolid != null && prevSolid.tokencode == TBaseType.rrw_with) { 343 hideToken(prevSolid); 344 } 345 } else if (wordIs(st, "referencing") && st.tokentype == ETokenType.ttidentifier && stmtHasInvalid) { 346 st.tokencode = TBaseType.rrw_exasol_referencing; 347 } else if (wordIs(st, "distribute") && next != null && next.tokencode == TBaseType.rrw_by) { 348 st.tokencode = TBaseType.rrw_exasol_distribute; 349 } else if (wordIs(st, "distribution") && prevSolid != null 350 && prevSolid.tokencode == TBaseType.rrw_drop && wordIs(next, "keys")) { 351 st.tokencode = TBaseType.rrw_exasol_distribute; 352 } else if (wordIs(st, "including") 353 && (wordIs(next, "defaults") || wordIs(next, "identity") || wordIs(next, "comments"))) { 354 st.tokencode = TBaseType.rrw_exasol_including; 355 } else if (st.tokencode == TBaseType.rrw_mssql_partition 356 && next != null && next.tokencode == TBaseType.rrw_by 357 && prevSolid != null && prevSolid.tokentype == ETokenType.ttcomma 358 && (wordIs(stmtFirst, "create") || wordIs(stmtFirst, "alter"))) { 359 // PARTITION BY as a CREATE/ALTER TABLE element (never after 360 // '(' — that is a window function's OVER(PARTITION BY ...)). 361 st.tokencode = TBaseType.rrw_exasol_partition; 362 } else if (st.tokencode == TBaseType.rrw_mssql_prior 363 && (prevSolid == null || prevSolid.tokencode != TBaseType.rrw_fetch)) { 364 st.tokencode = TBaseType.rrw_exasol_prior; 365 } else if ((wordIs(st, "disable") || wordIs(st, "enable")) 366 && prevSolid != null && prevSolid.tokentype == ETokenType.ttrightparenthesis) { 367 // constraint state after REFERENCES t (cols) 368 st.tokencode = TBaseType.rrw_exasol_disable; 369 } else if (wordIs(st, "admin") && st.tokentype == ETokenType.ttidentifier 370 && prevSolid != null && prevSolid.tokencode == TBaseType.rrw_with 371 && wordIs(next, "option")) { 372 st.tokencode = TBaseType.rrw_mssql_admin; 373 } else if (wordIs(st, "access") && st.tokentype == ETokenType.ttidentifier 374 && (wordIs(stmtFirst, "grant") || wordIs(stmtFirst, "revoke")) 375 && wordIs(next, "on")) { 376 st.tokencode = TBaseType.rrw_mssql_access; 377 } 378 } 379 prevSolid = st; 380 } 381 } 382 383 /** 384 * Merge client-style colon placeholders after ordinary SQL Server 385 * tokenization. Keeping this opt-in transformation out of the lexer leaves 386 * the default token stream, including token coordinates, byte-for-byte 387 * compatible with earlier releases. 388 */ 389 private void mergeMssqlColonBindVariables(TSourceTokenList tokens) { 390 for (int i = 0; i + 1 < tokens.size(); i++) { 391 TSourceToken colon = tokens.get(i); 392 TSourceToken suffix = tokens.get(i + 1); 393 if (colon.tokenstatus == ETokenStatus.tsdeleted 394 || suffix.tokenstatus == ETokenStatus.tsdeleted 395 || colon.tokencode != ':' 396 || colon.offset + colon.getAstext().length() != suffix.offset 397 || !isMssqlColonBindSuffix(suffix.getAstext())) { 398 continue; 399 } 400 colon.setString(colon.getAstext() + suffix.getAstext()); 401 colon.tokencode = TBaseType.bind_v; 402 colon.tokentype = ETokenType.ttbindvar; 403 suffix.tokenstatus = ETokenStatus.tsdeleted; 404 } 405 } 406 407 private boolean isMssqlColonBindSuffix(String text) { 408 if (text == null || text.isEmpty()) { 409 return false; 410 } 411 int start = text.charAt(0) == '@' ? 1 : 0; 412 if (start == text.length()) { 413 return false; 414 } 415 for (int i = start; i < text.length(); i++) { 416 char ch = text.charAt(i); 417 if (!(ch == '_' || ch >= 'A' && ch <= 'Z' 418 || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9')) { 419 return false; 420 } 421 } 422 return true; 423 } 424 425 private static boolean startsVerificationKind(TSourceToken t) { 426 if (t == null) return false; 427 TSourceToken t2 = t.nextSolidToken(); 428 if (wordIs(t, "unique")) { 429 return t2 != null && t2.tokentype == ETokenType.ttleftparenthesis; 430 } 431 if (wordIs(t, "primary") || wordIs(t, "foreign")) { 432 TSourceToken t3 = t2 == null ? null : t2.nextSolidToken(); 433 return wordIs(t2, "key") && t3 != null && t3.tokentype == ETokenType.ttleftparenthesis; 434 } 435 return false; 436 } 437 438 /** Case-insensitive command-word comparison (keywords, not identifiers). */ 439 private static boolean wordIs(TSourceToken t, String w) { 440 return t != null && t.toString().equalsIgnoreCase(w); 441 } 442 443 /** 444 * True when the routine collected so far is a CLR routine, i.e. its body is 445 * {@code AS EXTERNAL NAME assembly.class.method} rather than a BEGIN...END 446 * block. Such a statement ends at its semicolon; there is no body to wait for. 447 */ 448 private static boolean hasExternalNameBody(TCustomSqlStatement stmt) { 449 if (stmt == null) return false; 450 TSourceTokenList tokens = stmt.sourcetokenlist; 451 if (tokens == null) return false; 452 TSourceToken prevSolid = null; 453 for (int i = 0; i < tokens.size(); i++) { 454 TSourceToken t = tokens.get(i); 455 if (t.isnonsolidtoken()) continue; 456 // non-identifier-compare: keyword text. The splitter runs before the 457 // vendor parser, so the vendor-specific RW_EXTERNAL / RW_AS token codes 458 // are not available here; NAME reaches the grammar as a plain ColId. 459 if (wordIs(t, "external") && wordIs(prevSolid, "as")) { 460 return wordIs(t.nextSolidToken(), "name"); 461 } 462 prevSolid = t; 463 } 464 return false; 465 } 466 467 /** Neutralize a token for both the splitter and the parser. */ 468 private static void hideToken(TSourceToken t) { 469 t.tokentype = ETokenType.ttsimplecomment; 470 t.tokencode = TBaseType.cmtdoublehyphen; 471 } 472 473 /** 474 * Handle error recovery for CREATE TABLE/INDEX statements. 475 */ 476 private void handleCreateTableErrorRecovery(TCustomSqlStatement stmt) { 477 if (((stmt.sqlstatementtype == ESqlStatementType.sstcreatetable) 478 || (stmt.sqlstatementtype == ESqlStatementType.sstcreateindex)) 479 && (!TBaseType.c_createTableStrictParsing)) { 480 481 int nested = 0; 482 boolean isIgnore = false, isFoundIgnoreToken = false; 483 TSourceToken firstIgnoreToken = null; 484 485 for (int k = 0; k < stmt.sourcetokenlist.size(); k++) { 486 TSourceToken st = stmt.sourcetokenlist.get(k); 487 if (isIgnore) { 488 if (st.issolidtoken() && (st.tokencode != ';')) { 489 isFoundIgnoreToken = true; 490 if (firstIgnoreToken == null) { 491 firstIgnoreToken = st; 492 } 493 } 494 if (st.tokencode != ';') { 495 st.tokencode = TBaseType.sqlpluscmd; 496 } 497 continue; 498 } 499 if (st.tokencode == (int) ')') { 500 nested--; 501 if (nested == 0) { 502 boolean isSelect = false; 503 TSourceToken st1 = st.searchToken(TBaseType.rrw_as, 1); 504 if (st1 != null) { 505 TSourceToken st2 = st.searchToken((int) '(', 2); 506 if (st2 != null) { 507 TSourceToken st3 = st.searchToken(TBaseType.rrw_select, 3); 508 isSelect = (st3 != null); 509 } 510 } 511 if (!isSelect) isIgnore = true; 512 } 513 } else if (st.tokencode == (int) '(') { 514 nested++; 515 } 516 } 517 518 if (isFoundIgnoreToken) { 519 stmt.clearError(); 520 stmt.parsestatement(null, false); 521 } 522 } 523 } 524 525 /** 526 * Perform SQL Server-specific semantic analysis. 527 * <p> 528 * When called via delegation from TGSqlParser (context.getGsqlparser() != null), 529 * semantic analysis is skipped here and handled by TGSqlParser.doDelegatedParse() 530 * to ensure resolver2 is stored in TGSqlParser where tests expect to find it. 531 * <p> 532 * When called directly on MssqlSqlParser, uses TBaseType flags to determine 533 * which resolver to use: 534 * <ul> 535 * <li>TBaseType.isEnableResolver2() -> use TSQLResolver2</li> 536 * <li>TBaseType.isEnableResolver() -> use TSQLResolver</li> 537 * </ul> 538 */ 539 @Override 540 protected void performSemanticAnalysis(ParserContext context, TStatementList statements) { 541 // Skip semantic analysis when delegated from TGSqlParser 542 // TGSqlParser.doDelegatedParse() will handle it to store resolver2 properly 543 if (context != null && context.getGsqlparser() != null) { 544 return; 545 } 546 547 if (getSyntaxErrors().isEmpty()) { 548 if (TBaseType.isEnableResolver2()) { 549 // Use TSQLResolver2 550 TSQLResolverConfig config = new TSQLResolverConfig(); 551 config.setVendor(vendor); 552 TSQLResolver2 resolver2 = new TSQLResolver2(null, statements, config); 553 if (this.sqlEnv != null) { 554 resolver2.setSqlEnv(this.sqlEnv); 555 } 556 resolver2.resolve(); 557 } else if (TBaseType.isEnableResolver()) { 558 // Use TSQLResolver 559 TSQLResolver resolver = new TSQLResolver(globalContext, statements); 560 resolver.resolve(); 561 } 562 // If neither resolver is enabled, skip semantic analysis 563 } 564 } 565 566 /** 567 * Perform interpretation/evaluation on parsed statements. 568 */ 569 @Override 570 protected void performInterpreter(ParserContext context, TStatementList statements) { 571 if (TBaseType.ENABLE_INTERPRETER && getSyntaxErrors().isEmpty()) { 572 TLog.clearLogs(); 573 TGlobalScope interpreterScope = new TGlobalScope(sqlEnv); 574 TLog.enableInterpreterLogOnly(); 575 TASTEvaluator astEvaluator = new TASTEvaluator(statements, interpreterScope); 576 astEvaluator.eval(); 577 } 578 } 579 580 // ========== SQL Server-Specific Tokenization ========== 581 582 /** 583 * SQL Server-specific tokenization logic. 584 * <p> 585 * Extracted from: TGSqlParser.domssqlsqltexttotokenlist() (lines 2225-2425) 586 */ 587 private void domssqlsqltexttotokenlist() { 588 TSourceToken lcprevtoken = null; 589 int lcsteps = 0; 590 TSourceToken asourcetoken, lctoken, lctoken2, lctoken3; 591 int yychar; 592 boolean iskeywordgo; 593 594 asourcetoken = getanewsourcetoken(); 595 596 if (asourcetoken == null) return; 597 598 yychar = asourcetoken.tokencode; 599 600 boolean lcinopenrowset = false; 601 int lcnested = 0; 602 603 // RENAME is a keyword only where a statement or clause starts with it: 604 // RENAME OBJECT / RENAME DATABASE (Synapse, PDW) and ALTER TABLE ... 605 // RENAME COLUMN / CONSTRAINT. SQL Server does not reserve the word, so 606 // anywhere else it is an identifier, e.g. a table alias (Mantis 4959). 607 // The grammar cannot fall back to an identifier there: RENAME OBJECT may 608 // start the next statement, so the parser reduces on RENAME instead of 609 // shifting it as an alias. Decided when the next solid token arrives. 610 TSourceToken lcpendingrename = null; 611 boolean lcrenamecanbeident = (parserContext != null) 612 && ((parserContext.getVendor() == EDbVendor.dbvmssql) 613 || (parserContext.getVendor() == EDbVendor.dbvazuresql)); 614 615 while (yychar > 0) { 616 617 if ((lcpendingrename != null) && asourcetoken.issolidtoken()) { 618 if (!isRenameStatementWord(asourcetoken)) { 619 lcpendingrename.tokencode = TBaseType.ident; 620 lcpendingrename.tokentype = ETokenType.ttidentifier; 621 } 622 lcpendingrename = null; 623 } 624 if (lcrenamecanbeident && (asourcetoken.tokencode == TBaseType.rrw_rename)) { 625 lcpendingrename = asourcetoken; 626 } 627 628 if (asourcetoken.tokencode == TBaseType.rrw_openrowset) { 629 // openrowset(....) 630 lcinopenrowset = true; 631 lcnested = 0; 632 } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) { 633 if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) { 634 if (lcprevtoken.tokencode == TBaseType.rrw_primary) { 635 lcprevtoken.tokencode = TBaseType.rrw_select - 2; //rw_primary2 636 checkconstarinttoken(lcprevtoken); 637 } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) { 638 lcprevtoken.tokencode = TBaseType.rrw_select - 4; //rw_foreign2 639 checkconstarinttoken(lcprevtoken); 640 } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) { 641 lcprevtoken.tokencode = TBaseType.rrw_select - 1; //rw_unique2 642 checkconstarinttoken(lcprevtoken); 643 } 644 lcprevtoken = null; 645 lcsteps = 0; 646 } 647 648 // openrowset(....) 649 if (lcinopenrowset) 650 lcnested++; 651 } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) { 652 // openrowset(....) 653 if (lcinopenrowset) { 654 if ((lcnested > 0)) 655 lcnested--; 656 if (lcnested == 0) 657 lcinopenrowset = false; 658 } 659 } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 660 if (lcinopenrowset) { 661 asourcetoken.tokentype = ETokenType.ttsemicolon2; 662 } else { 663 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1); 664 if (lctoken2 != null) { 665 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 666 asourcetoken.tokentype = ETokenType.ttsemicolon3; 667 } else { 668 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1); 669 if (lctoken2 == null) { 670 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1); 671 } 672 if (lctoken2 != null) { 673 lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2); 674 if (lctoken3 != null) { 675 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 676 asourcetoken.tokentype = ETokenType.ttsemicolon3; 677 } 678 } 679 } 680 } 681 682 lctoken = getprevtoken(asourcetoken); 683 if ((lctoken != null)) { 684 if (lctoken.tokentype == ETokenType.ttsemicolon) { 685 // treat this semicolon as a whitespace 686 asourcetoken.tokencode = TBaseType.lexspace; 687 } 688 } 689 } else if (asourcetoken.tokentype == ETokenType.ttperiod) { 690 lctoken = getprevtoken(asourcetoken); 691 if (TBaseType.assigned(lctoken)) { 692 // go.fieldname, go is a table alias, not a go statement 693 if (lctoken.tokencode == TBaseType.rrw_go) { 694 lctoken.tokencode = TBaseType.ident; 695 lctoken.tokentype = ETokenType.ttidentifier; 696 } 697 } 698 } else if (asourcetoken.tokencode == TBaseType.rrw_table) { 699 lctoken = getprevtoken(asourcetoken); 700 if (TBaseType.assigned(lctoken)) { 701 if (lctoken.tokencode == TBaseType.rrw_lock) { 702 lctoken.tokencode = TBaseType.rw_locktable; 703 } 704 } 705 } else if (asourcetoken.tokencode == TBaseType.rrw_into) { 706 lctoken = getprevtoken(asourcetoken); 707 if (TBaseType.assigned(lctoken)) { 708 if (lctoken.tokencode == TBaseType.rrw_sqlserver_copy) { 709 lctoken.tokencode = TBaseType.rrw_sqlserver_copyinto; 710 } 711 } 712 } else if (asourcetoken.tokencode == TBaseType.rrw_sqlserver_column) { 713 lctoken = getprevtoken(asourcetoken); 714 if (TBaseType.assigned(lctoken)) { 715 if (lctoken.tokencode == TBaseType.rrw_drop) { 716 asourcetoken.tokencode = TBaseType.rrw_sqlserver_drop_column; 717 } 718 } 719 } else if (asourcetoken.tokencode == TBaseType.rrw_go) { 720 iskeywordgo = true; 721 lctoken = getprevtoken(asourcetoken); 722 if (TBaseType.assigned(lctoken)) { 723 // go should not at same line as other sql statement 724 if ((lctoken.lineNo == asourcetoken.lineNo) && (lctoken.tokencode != ';')) { 725 iskeywordgo = false; 726 } 727 } 728 729 if (iskeywordgo) { 730 lcinopenrowset = false; 731 lcnested = 0; 732 lcprevtoken = asourcetoken; 733 } else { 734 asourcetoken.tokencode = TBaseType.ident; 735 asourcetoken.tokentype = ETokenType.ttidentifier; 736 } 737 } else if (asourcetoken.tokencode == TBaseType.rrw_primary) { 738 // primary key [clustered | nonclustered ] [hash] ( column list) 739 lcsteps = 2; 740 lcprevtoken = asourcetoken; 741 } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) { 742 // foreign key [clustered | nonclustered ] ( column list) 743 lcsteps = 2; 744 lcprevtoken = asourcetoken; 745 } else if (asourcetoken.tokencode == TBaseType.rrw_unique) { 746 // unique [clustered | nonclustered ] [hash] ( column list) 747 lcsteps = 1; 748 lcprevtoken = asourcetoken; 749 } else if (asourcetoken.issolidtoken()) { 750 if (lcsteps > 0) { 751 if (!(TBaseType.mysametext("clustered", asourcetoken.toString()) 752 || TBaseType.mysametext("nonclustered", asourcetoken.toString()) 753 || TBaseType.mysametext("hash", asourcetoken.toString()) 754 )) 755 lcsteps--; 756 } 757 } 758 759 sourcetokenlist.add(asourcetoken); 760 761 asourcetoken = getanewsourcetoken(); 762 if (asourcetoken == null) break; 763 yychar = asourcetoken.tokencode; 764 } 765 766 // RENAME as the last solid token of the input: an identifier 767 if (lcpendingrename != null) { 768 lcpendingrename.tokencode = TBaseType.ident; 769 lcpendingrename.tokentype = ETokenType.ttidentifier; 770 } 771 } 772 773 /** 774 * True for the word after RENAME that makes RENAME a keyword: RENAME OBJECT, 775 * RENAME DATABASE, and ALTER TABLE ... RENAME COLUMN / CONSTRAINT. 776 */ 777 private static boolean isRenameStatementWord(TSourceToken next) { 778 return wordIs(next, "object") || wordIs(next, "database") 779 || wordIs(next, "column") || wordIs(next, "constraint"); 780 } 781 782 /** 783 * Get previous token in token list. 784 * <p> 785 * Matches TGSqlParser.getprevtoken() implementation exactly to ensure 786 * consistent tokenization behavior. 787 */ 788 private TSourceToken getprevtoken(TSourceToken ptoken) { 789 TSourceTokenList lcstlist = ptoken.container; 790 if (TBaseType.assigned(lcstlist)) { 791 if ((ptoken.posinlist > 0) && (lcstlist.size() > ptoken.posinlist - 1)) { 792 if (!((lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 793 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 794 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 795 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment) 796 )) { 797 return lcstlist.get(ptoken.posinlist - 1); 798 } else { 799 return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 800 } 801 } 802 } 803 return null; 804 } 805 806 /** 807 * Check and adjust constraint token codes. 808 * <p> 809 * When PRIMARY KEY, FOREIGN KEY, or UNIQUE is followed by '(', we need to 810 * look back to see if there's a CONSTRAINT keyword. If found, change its 811 * token code from rrw_constraint (515) to rw_constraint2 (298). 812 * <p> 813 * Extracted from: TGSqlParser.checkconstarinttoken() (lines 1989-2005) 814 * 815 * @param token The token after which to search (PRIMARY/FOREIGN/UNIQUE) 816 */ 817 private void checkconstarinttoken(TSourceToken token) { 818 TSourceTokenList lcStList = token.container; 819 if (TBaseType.assigned(lcStList)) { 820 // Look back 2 solid tokens to find CONSTRAINT keyword 821 TSourceToken lcPPToken = lcStList.nextsolidtoken(token.posinlist, -2, false); 822 if (TBaseType.assigned(lcPPToken)) { 823 if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint")) { 824 // Change CONSTRAINT to special constraint2 token code 825 lcPPToken.tokencode = TBaseType.rw_constraint2; 826 } 827 } 828 } 829 } 830 831 // ========== SQL Server-Specific Raw Statement Extraction ========== 832 833 /** 834 * SQL Server-specific raw statement extraction logic. 835 * <p> 836 * Extracted from: TGSqlParser.domssqlgetrawsqlstatements() (lines 13081-13904) 837 */ 838 private void domssqlgetrawsqlstatements(SqlParseResult.Builder builder) { 839 int errorcount = 0; 840 int case_end_nest = 0; 841 842 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 843 if (!TBaseType.assigned(sourcetokenlist)) { 844 builder.errorCode(-1); 845 builder.errorMessage("Source token list is null"); 846 return; 847 } 848 849 gcurrentsqlstatement = null; 850 EFindSqlStateType gst = EFindSqlStateType.stnormal; 851 int lcblocklevel = 0; 852 int lctrycatchlevel = 0; 853 TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken; 854 TSourceToken ast = null; 855 int i, lcMergeInSelectNested = 0; 856 boolean lcisendconversation, lcstillinsql, lcMergeInSelect = false; 857 858 for (i = 0; i < sourcetokenlist.size(); i++) { 859 860 if ((ast != null) && (ast.issolidtoken())) 861 lcprevsolidtoken = ast; 862 863 ast = sourcetokenlist.get(i); 864 sourcetokenlist.curpos = i; 865 866 if (ast.tokencode == TBaseType.rrw_for) { 867 TSourceToken st1 = ast.nextSolidToken(); 868 if ((st1 != null) && (st1.tokencode == TBaseType.rrw_system_time)) { 869 ast.tokencode = TBaseType.rw_for_system_time; 870 } else if ((st1 != null) && TBaseType.mycomparetext(st1.getAstext(), "PATH") == 0) { 871 ast.tokencode = TBaseType.rw_for_path; 872 } 873 } 874 875 if (lcMergeInSelect) { 876 if (ast.tokencode == '(') lcMergeInSelectNested++; 877 if (ast.tokencode == ')') { 878 lcMergeInSelectNested--; 879 if (lcMergeInSelectNested == 0) { 880 lcMergeInSelect = false; 881 } 882 } 883 gcurrentsqlstatement.sourcetokenlist.add(ast); 884 continue; 885 } 886 887 if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) { 888 gcurrentsqlstatement.sourcetokenlist.add(ast); 889 continue; 890 } 891 892 if (ast.tokencode == TBaseType.rrw_minus) { 893 TSourceToken st1 = ast.searchToken('(', 1); 894 if (st1 == null) { 895 st1 = ast.searchToken(TBaseType.rrw_select, 1); 896 if (st1 == null) { 897 ast.tokencode = TBaseType.ident; 898 } 899 } 900 } else if (ast.tokencode == TBaseType.rrw_merge) { 901 TSourceToken st1 = ast.nextSolidToken(); 902 if (st1.tokencode == TBaseType.rrw_join) { 903 ast.tokencode = TBaseType.rrw_merge2_sqlserver; 904 } 905 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '(')) { 906 lcMergeInSelect = true; 907 lcMergeInSelectNested++; 908 gcurrentsqlstatement.sourcetokenlist.add(ast); 909 continue; 910 } 911 } else if (ast.tokencode == TBaseType.rrw_sqlserver_value) { 912 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 913 TSourceToken st1 = ast.searchToken('(', 1); 914 if (st1 != null) { 915 ast.tokencode = TBaseType.rrw_xml_value; 916 } 917 } 918 } else if (ast.tokencode == TBaseType.rrw_sqlserver_modify) { 919 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 920 TSourceToken st1 = ast.searchToken('(', 1); 921 if (st1 != null) { 922 ast.tokencode = TBaseType.rrw_xml_modify; 923 } 924 } 925 } else if (ast.tokencode == TBaseType.rrw_sqlserver_query) { 926 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 927 TSourceToken st1 = ast.searchToken('(', 1); 928 if (st1 != null) { 929 ast.tokencode = TBaseType.rrw_xml_query; 930 } 931 } 932 } else if (ast.tokencode == TBaseType.rrw_sqlserver_exist) { 933 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 934 TSourceToken st1 = ast.searchToken('(', 1); 935 if (st1 != null) { 936 ast.tokencode = TBaseType.rrw_xml_exist; 937 } 938 } 939 } else if (ast.tokencode == TBaseType.rrw_sqlserver_nodes) { 940 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 941 TSourceToken st1 = ast.searchToken('(', 1); 942 if (st1 != null) { 943 ast.tokencode = TBaseType.rrw_xml_nodes; 944 } 945 } 946 } else if (ast.tokencode == TBaseType.ident && ast.tokentype == ETokenType.ttdbstring) { 947 // Handle bracket-quoted XML method names: .[value](), .[nodes](), etc. 948 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 949 TSourceToken st1 = ast.searchToken('(', 1); 950 if (st1 != null) { 951 String text = ast.toString(); 952 if (text.length() > 2) { 953 String inner = text.substring(1, text.length() - 1).toLowerCase(java.util.Locale.ROOT); 954 if (inner.equals("value")) { 955 ast.tokencode = TBaseType.rrw_xml_value; 956 } else if (inner.equals("modify")) { 957 ast.tokencode = TBaseType.rrw_xml_modify; 958 } else if (inner.equals("query")) { 959 ast.tokencode = TBaseType.rrw_xml_query; 960 } else if (inner.equals("exist")) { 961 ast.tokencode = TBaseType.rrw_xml_exist; 962 } else if (inner.equals("nodes")) { 963 ast.tokencode = TBaseType.rrw_xml_nodes; 964 } 965 } 966 } 967 } 968 } else if (ast.tokencode == TBaseType.rrw_check) { 969 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == TBaseType.rrw_with)) { 970 lcprevsolidtoken.tokencode = TBaseType.rrw_sqlserver_check_with; 971 } 972 } else if (ast.tokencode == TBaseType.rrw_sqlserver_next) { 973 TSourceToken st1 = ast.nextSolidToken(); 974 if ((st1 != null) && (st1.tokencode == '.')) { 975 ast.tokencode = TBaseType.ident; 976 } 977 } else if (ast.tokencode == TBaseType.rrw_fetch) { 978 if ((lcprevsolidtoken != null) && ((lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_row) || (lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_rows))) { 979 TSourceToken prev2 = lcprevsolidtoken.searchToken(TBaseType.rrw_open, -1); 980 if (prev2 == null) { 981 ast.tokencode = TBaseType.rrw_sqlserver_offset_fetch; 982 } 983 } 984 } else if ((ast.tokencode == TBaseType.rrw_exec) || (ast.tokencode == TBaseType.rrw_execute)) { 985 // search ;2 after execute 986 // EXECUTE ApxSQL_Test_Triggers_Add;2 @TableName, @AddFlag 987 int searchRange = 4; 988 TSourceToken endTokenInSameLine = ast.searchTokenAtTheEndOfSameLine(); 989 if (endTokenInSameLine != null) { 990 searchRange = endTokenInSameLine.posinlist - ast.posinlist; 991 } 992 TSourceToken st1 = ast.searchToken(';', searchRange); 993 if (st1 != null) { 994 TSourceToken numSt = st1.nextSolidToken(); 995 if ((numSt != null) && (numSt.tokentype == ETokenType.ttnumber)) { 996 st1.tokencode = TBaseType.rrw_sqlserver_semicolon_module_number; 997 st1.tokentype = ETokenType.ttidentifier; 998 } 999 } 1000 } else if (ast.tokencode == TBaseType.rrw_sqlserver_trim) { 1001 TSourceToken st1 = ast.nextSolidToken(); 1002 if (st1 != null) { 1003 if (st1.tokencode == '(') { 1004 // keep as trim keyword 1005 } else { 1006 ast.tokencode = TBaseType.ident; 1007 } 1008 } 1009 } 1010 1011 if (vendor == EDbVendor.dbvopenedge){ 1012 if (ast.tokencode == TBaseType.rrw_order){ 1013 TSourceToken st1 = ast.searchToken(TBaseType.rrw_by,1); 1014 if (st1 == null) { 1015 ast.tokencode = TBaseType.ident; 1016 } 1017 }else if (ast.tokencode == TBaseType.rrw_with){ 1018 TSourceToken st1 = ast.searchToken(TBaseType.rrw_check,1); 1019 if (st1 != null) { 1020 ast.tokencode = TBaseType.rrw_openedge_with_check; 1021 } 1022 } 1023 } 1024 1025 if (gst == EFindSqlStateType.ststoredprocedurebody) { 1026 if (!((ast.tokencode == TBaseType.rrw_go) 1027 || (ast.tokencode == TBaseType.rrw_create) 1028 || (ast.tokencode == TBaseType.rrw_alter))) { 1029 gcurrentsqlstatement.sourcetokenlist.add(ast); 1030 continue; 1031 } 1032 } 1033 1034 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 1035 1036 switch (gst) { 1037 case sterror: { 1038 if (TBaseType.assigned(lcnextsqlstatement)) { 1039 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1040 gcurrentsqlstatement = lcnextsqlstatement; 1041 gcurrentsqlstatement.sourcetokenlist.add(ast); 1042 gst = EFindSqlStateType.stsql; 1043 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 1044 gcurrentsqlstatement.sourcetokenlist.add(ast); 1045 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1046 gst = EFindSqlStateType.stnormal; 1047 } else { 1048 gcurrentsqlstatement.sourcetokenlist.add(ast); 1049 } 1050 break; 1051 } 1052 case stnormal: { 1053 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 1054 || (ast.tokencode == TBaseType.cmtslashstar) 1055 || (ast.tokencode == TBaseType.lexspace) 1056 || (ast.tokencode == TBaseType.lexnewline) 1057 || (ast.tokentype == ETokenType.ttsemicolon)) { 1058 if (TBaseType.assigned(gcurrentsqlstatement)) { 1059 gcurrentsqlstatement.sourcetokenlist.add(ast); 1060 } 1061 1062 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 1063 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 1064 // ;;;; continuous semicolon,treat it as comment 1065 ast.tokentype = ETokenType.ttsimplecomment; 1066 ast.tokencode = TBaseType.cmtdoublehyphen; 1067 } 1068 } 1069 1070 continue; 1071 } 1072 1073 gcurrentsqlstatement = lcnextsqlstatement; 1074 1075 if (TBaseType.assigned(gcurrentsqlstatement)) { 1076 switch (gcurrentsqlstatement.sqlstatementtype) { 1077 case sstmssqlcreateprocedure: 1078 case sstmssqlcreatefunction: 1079 case sstcreatetrigger: 1080 case sstmssqlalterprocedure: 1081 case sstmssqlalterfunction: 1082 case sstmssqlaltertrigger: { 1083 gcurrentsqlstatement.sourcetokenlist.add(ast); 1084 gst = EFindSqlStateType.ststoredprocedure; 1085 break; 1086 } 1087 case sstmssqlbegintry: 1088 case sstmssqlbegincatch: { 1089 gcurrentsqlstatement.sourcetokenlist.add(ast); 1090 gst = EFindSqlStateType.sttrycatch; 1091 lctrycatchlevel = 0; 1092 break; 1093 } 1094 case sstmssqlgo: { 1095 gcurrentsqlstatement.sourcetokenlist.add(ast); 1096 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1097 gst = EFindSqlStateType.stnormal; 1098 break; 1099 } 1100 default: { 1101 gcurrentsqlstatement.sourcetokenlist.add(ast); 1102 gst = EFindSqlStateType.stsql; 1103 break; 1104 } 1105 } 1106 } else { 1107 if (ast.tokencode == TBaseType.rrw_begin) { 1108 gcurrentsqlstatement = new TMssqlBlock(vendor); 1109 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 1110 gcurrentsqlstatement.sourcetokenlist.add(ast); 1111 gst = EFindSqlStateType.stblock; 1112 } else { 1113 if (sqlstatements.size() == 0) { 1114 //first statement of mssql batch, treat it as exec sp 1115 gst = EFindSqlStateType.stsql; 1116 gcurrentsqlstatement = new TMssqlExecute(vendor); 1117 gcurrentsqlstatement.sourcetokenlist.add(ast); 1118 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1119 // prev sql is go, treat it as exec sp 1120 gst = EFindSqlStateType.stsql; 1121 gcurrentsqlstatement = new TMssqlExecute(vendor); 1122 gcurrentsqlstatement.sourcetokenlist.add(ast); 1123 } 1124 } 1125 } 1126 1127 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 1128 { 1129 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 1130 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 1131 1132 ast.tokentype = ETokenType.tttokenlizererrortoken; 1133 gst = EFindSqlStateType.sterror; 1134 1135 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 1136 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 1137 gcurrentsqlstatement.sourcetokenlist.add(ast); 1138 } 1139 break; 1140 } 1141 case stblock: { 1142 if (TBaseType.assigned(lcnextsqlstatement)) { 1143 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1144 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1145 gcurrentsqlstatement = lcnextsqlstatement; 1146 gcurrentsqlstatement.sourcetokenlist.add(ast); 1147 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1148 gst = EFindSqlStateType.stnormal; 1149 } else { 1150 lcnextsqlstatement = null; 1151 } 1152 } 1153 1154 if (gst == EFindSqlStateType.stblock) { 1155 gcurrentsqlstatement.sourcetokenlist.add(ast); 1156 if (ast.tokencode == TBaseType.rrw_begin) { 1157 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 1158 if (TBaseType.assigned(lcnextsolidtoken)) { 1159 if (!((lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_tran) 1160 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_transaction) 1161 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_distributed) 1162 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_dialog) 1163 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation) 1164 )) 1165 lcblocklevel++; 1166 } else 1167 lcblocklevel++; 1168 1169 } else if (ast.tokencode == TBaseType.rrw_case) 1170 lcblocklevel++; 1171 else if (ast.tokencode == TBaseType.rrw_end) { 1172 1173 lcisendconversation = false; 1174 1175 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 1176 if (TBaseType.assigned(lcnextsolidtoken)) { 1177 if (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation) 1178 lcisendconversation = true; 1179 } 1180 1181 if (!lcisendconversation) { 1182 1183 if (lcblocklevel == 0) { 1184 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1185 if (TBaseType.assigned(lcnextsolidtoken)) { 1186 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 1187 gst = EFindSqlStateType.stsql; 1188 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 1189 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 1190 if (TBaseType.assigned(lcnnextsolidtoken)) { 1191 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 1192 gst = EFindSqlStateType.stsql; 1193 } 1194 } 1195 } 1196 } 1197 } 1198 1199 if (gst != EFindSqlStateType.stsql) { 1200 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1201 gst = EFindSqlStateType.stnormal; 1202 } 1203 1204 } else { 1205 lcblocklevel--; 1206 } 1207 1208 } 1209 } 1210 } 1211 break; 1212 } 1213 case sttrycatch: { 1214 1215 if (TBaseType.assigned(lcnextsqlstatement)) { 1216 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1217 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1218 gcurrentsqlstatement = lcnextsqlstatement; 1219 gcurrentsqlstatement.sourcetokenlist.add(ast); 1220 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1221 gst = EFindSqlStateType.stnormal; 1222 } else { 1223 if ( 1224 (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry) 1225 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 1226 ) 1227 lctrycatchlevel++; 1228 lcnextsqlstatement = null; 1229 } 1230 } 1231 1232 if (gst == EFindSqlStateType.sttrycatch) { 1233 gcurrentsqlstatement.sourcetokenlist.add(ast); 1234 if ((ast.tokencode == TBaseType.rrw_try) || 1235 (ast.tokencode == TBaseType.rrw_catch)) { 1236 if (TBaseType.assigned(lcprevsolidtoken)) { 1237 if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) { 1238 if (lctrycatchlevel == 0) { 1239 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1240 gst = EFindSqlStateType.stnormal; 1241 } else 1242 lctrycatchlevel--; 1243 } 1244 } 1245 } 1246 } 1247 break; 1248 } 1249 case stprocedureWithReturn: { 1250 // found return statement in create procedure/function 1251 1252 if (TBaseType.assigned(lcnextsqlstatement)) { 1253 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1254 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1255 gcurrentsqlstatement = lcnextsqlstatement; 1256 gcurrentsqlstatement.sourcetokenlist.add(ast); 1257 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1258 gst = EFindSqlStateType.stnormal; 1259 break; 1260 } 1261 } 1262 1263 gcurrentsqlstatement.sourcetokenlist.add(ast); 1264 if ((gst == EFindSqlStateType.stprocedureWithReturn) && (ast.tokentype == ETokenType.ttsemicolon)) { 1265 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1266 gst = EFindSqlStateType.stnormal; 1267 } 1268 break; 1269 } 1270 case stsql: { 1271 if ((ast.tokentype == ETokenType.ttsemicolon)) { 1272 lcstillinsql = false; 1273 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1274 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 1275 if (TBaseType.assigned(lcnextsolidtoken)) { 1276 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 1277 // if ( expr stmt; else 1278 gcurrentsqlstatement.sourcetokenlist.add(ast); 1279 lcstillinsql = true; 1280 } 1281 1282 } 1283 } 1284 1285 if (!lcstillinsql) { 1286 gst = EFindSqlStateType.stnormal; 1287 gcurrentsqlstatement.sourcetokenlist.add(ast); 1288 gcurrentsqlstatement.semicolonended = ast; 1289 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1290 } 1291 1292 } else if (TBaseType.assigned(lcnextsqlstatement)) { 1293 1294 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1295 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1296 gcurrentsqlstatement = lcnextsqlstatement; 1297 gcurrentsqlstatement.sourcetokenlist.add(ast); 1298 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1299 gst = EFindSqlStateType.stnormal; 1300 continue; 1301 } 1302 1303 // A typed TUnknownSqlStatement (admin DDL recognized by 1304 // the splitter, no deep AST) has no internal statement 1305 // structure: it runs to its semicolon. Without this, 1306 // ALTER EXTERNAL LANGUAGE ... SET (...) split at SET 1307 // because SET is a registered statement head. 1308 if ((gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TUnknownSqlStatement) 1309 && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstinvalid) 1310 && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstunknown)) { 1311 gcurrentsqlstatement.sourcetokenlist.add(ast); 1312 continue; 1313 } 1314 1315 switch (gcurrentsqlstatement.sqlstatementtype) { 1316 case sstmssqlif: 1317 case sstmssqlwhile: { 1318 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 1319 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) { 1320 gcurrentsqlstatement.sourcetokenlist.add(ast); 1321 gst = EFindSqlStateType.stblock; 1322 lcblocklevel = 1; 1323 lcnextsqlstatement = null; 1324 continue; 1325 1326 } else if (gcurrentsqlstatement.dummytag == 1) { 1327 // if ( cond ^stmt nextstmt (^ stands for current pos) 1328 gcurrentsqlstatement.sourcetokenlist.add(ast); 1329 1330 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1331 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) 1332 gcurrentsqlstatement.dummytag = 1; 1333 else 1334 gcurrentsqlstatement.dummytag = 0; 1335 1336 lcnextsqlstatement = null; 1337 continue; 1338 } else { 1339 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1340 if ((ast.nextSolidToken() != null) && (ast.nextSolidToken().tokencode == TBaseType.rrw_sqlserver_exists)) { 1341 gcurrentsqlstatement.sourcetokenlist.add(ast); 1342 lcnextsqlstatement = null; 1343 continue; 1344 } 1345 } 1346 } 1347 break; 1348 } 1349 case sstmssqlalterqueue: { 1350 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) { 1351 // execute can't be used to delimite alter queue 1352 gcurrentsqlstatement.sourcetokenlist.add(ast); 1353 lcnextsqlstatement = null; 1354 continue; 1355 1356 } 1357 break; 1358 } 1359 case sstmssqlcreateschema: { 1360 gcurrentsqlstatement.sourcetokenlist.add(ast); 1361 lcnextsqlstatement = null; 1362 continue; 1363 } 1364 } 1365 1366 // "IF EXISTS <name>" belongs to the statement it 1367 // follows (DROP DEFAULT d IF EXISTS ...). But 1368 // "IF EXISTS (SELECT ...)" is control flow starting a 1369 // new statement - real scripts chain unterminated 1370 // statements before it - so the '(' after EXISTS 1371 // decides. 1372 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1373 TSourceToken existsTok = ast.nextSolidToken(); 1374 if ((existsTok != null) 1375 && (existsTok.tokencode == TBaseType.rrw_sqlserver_exists)) { 1376 TSourceToken afterExists = existsTok.nextSolidToken(); 1377 if ((afterExists == null) 1378 || (afterExists.tokentype != ETokenType.ttleftparenthesis)) { 1379 gcurrentsqlstatement.sourcetokenlist.add(ast); 1380 continue; 1381 } 1382 } 1383 } 1384 1385 // ALTER PARTITION FUNCTION f() MERGE|SPLIT RANGE (...): 1386 // the MERGE keyword is the function operation, not a 1387 // MERGE statement. 1388 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlAlterPartitionFunction) 1389 && (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmerge)) { 1390 gcurrentsqlstatement.sourcetokenlist.add(ast); 1391 continue; 1392 } 1393 1394 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1395 gcurrentsqlstatement = lcnextsqlstatement; 1396 gcurrentsqlstatement.sourcetokenlist.add(ast); 1397 1398 switch (gcurrentsqlstatement.sqlstatementtype) { 1399 case sstmssqlcreateprocedure: 1400 case sstmssqlcreatefunction: 1401 case sstcreatetrigger: 1402 case sstmssqlalterprocedure: 1403 case sstmssqlalterfunction: 1404 case sstmssqlaltertrigger: { 1405 gst = EFindSqlStateType.ststoredprocedure; 1406 break; 1407 } 1408 case sstmssqlbegintry: 1409 case sstmssqlbegincatch: { 1410 gst = EFindSqlStateType.sttrycatch; 1411 lctrycatchlevel = 0; 1412 break; 1413 } 1414 case sstmssqlgo: { 1415 gst = EFindSqlStateType.stnormal; 1416 break; 1417 } 1418 default: { 1419 gst = EFindSqlStateType.stsql; 1420 break; 1421 } 1422 } 1423 1424 } else if ((ast.tokencode == TBaseType.rrw_begin)) { 1425 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1426 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) { 1427 gst = EFindSqlStateType.stblock; 1428 lcblocklevel = 0; 1429 gcurrentsqlstatement.sourcetokenlist.add(ast); 1430 } else if (case_end_nest > 0) { 1431 // Inside a CASE expression — BEGIN cannot be a top-level block boundary 1432 gcurrentsqlstatement.sourcetokenlist.add(ast); 1433 } else { 1434 // mantisbt 4411: a plain BEGIN ... END after a complete statement 1435 // (EXEC, SELECT, UPDATE, SET, DECLARE, PRINT, RAISERROR, label, ...) 1436 // starts a new top-level TMssqlBlock. Previously the else branch 1437 // silently swallowed BEGIN into the preceding statement, breaking 1438 // the parse for batches like: 1439 // EXEC dbo.p1 1440 // BEGIN UPDATE x SET y = 1 END 1441 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1442 gcurrentsqlstatement = new TMssqlBlock(vendor); 1443 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 1444 gcurrentsqlstatement.sourcetokenlist.add(ast); 1445 gst = EFindSqlStateType.stblock; 1446 } 1447 } else if ((ast.tokencode == TBaseType.rrw_case)) { 1448 case_end_nest++; 1449 gcurrentsqlstatement.sourcetokenlist.add(ast); 1450 } else if ((ast.tokencode == TBaseType.rrw_end)) { 1451 if (case_end_nest > 0) { 1452 case_end_nest--; 1453 } 1454 gcurrentsqlstatement.sourcetokenlist.add(ast); 1455 } else if ((ast.tokencode == TBaseType.rrw_else)) { 1456 gcurrentsqlstatement.sourcetokenlist.add(ast); 1457 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1458 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 1459 ) && (case_end_nest == 0)) 1460 gcurrentsqlstatement.dummytag = 1; 1461 } else { 1462 gcurrentsqlstatement.sourcetokenlist.add(ast); 1463 } 1464 break; 1465 } 1466 case ststoredprocedure: { 1467 if (TBaseType.assigned(lcnextsqlstatement)) { 1468 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1469 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1470 gcurrentsqlstatement = lcnextsqlstatement; 1471 gcurrentsqlstatement.sourcetokenlist.add(ast); 1472 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1473 gst = EFindSqlStateType.stnormal; 1474 } else if (lcnextsqlstatement.sqlstatementtype == sstmssqlreturn) { 1475 gst = EFindSqlStateType.stprocedureWithReturn; 1476 gcurrentsqlstatement.sourcetokenlist.add(ast); 1477 lcnextsqlstatement = null; 1478 } else { 1479 gst = EFindSqlStateType.ststoredprocedurebody; 1480 gcurrentsqlstatement.sourcetokenlist.add(ast); 1481 1482 lcnextsqlstatement = null; 1483 } 1484 } 1485 1486 if (gst == EFindSqlStateType.ststoredprocedure) { 1487 gcurrentsqlstatement.sourcetokenlist.add(ast); 1488 if (ast.tokencode == TBaseType.rrw_begin) { 1489 gst = EFindSqlStateType.stblock; 1490 } else if ((ast.tokentype == ETokenType.ttsemicolon) 1491 && hasExternalNameBody(gcurrentsqlstatement)) { 1492 // CLR routine: AS EXTERNAL NAME assembly.class.method has no 1493 // BEGIN...END body, so the semicolon ends the statement. Without 1494 // this the splitter keeps waiting for a body and swallows the 1495 // next statement of the script. 1496 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1497 gst = EFindSqlStateType.stnormal; 1498 } 1499 } 1500 break; 1501 } 1502 case ststoredprocedurebody: { 1503 if (TBaseType.assigned(lcnextsqlstatement)) { 1504 switch (lcnextsqlstatement.sqlstatementtype) { 1505 case sstmssqlgo: { 1506 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1507 gcurrentsqlstatement = lcnextsqlstatement; 1508 gcurrentsqlstatement.sourcetokenlist.add(ast); 1509 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1510 gst = EFindSqlStateType.stnormal; 1511 break; 1512 } 1513 case sstmssqlcreateprocedure: 1514 case sstmssqlcreatefunction: 1515 case sstcreatetrigger: 1516 case sstmssqlalterprocedure: 1517 case sstmssqlalterfunction: 1518 case sstmssqlaltertrigger: { 1519 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1520 gcurrentsqlstatement = lcnextsqlstatement; 1521 gcurrentsqlstatement.sourcetokenlist.add(ast); 1522 gst = EFindSqlStateType.ststoredprocedure; 1523 break; 1524 } 1525 case sstcreateview: 1526 case sstcreatetable: { 1527 1528 boolean readForNewStmt = false; 1529 TSourceToken st1 = ast.searchToken(';', -1); 1530 if (st1 != null) { 1531 TSourceToken st2 = ast.searchToken(TBaseType.rrw_end, -2); 1532 if (st2 != null) { 1533 // Verify this END is at block level 0 (not inside a nested 1534 // BEGIN...END or BEGIN TRY...END TRY block) by counting 1535 // BEGINs and ENDs in the current proc's token list. 1536 int blockDepth = 0; 1537 for (int k = 0; k < gcurrentsqlstatement.sourcetokenlist.size(); k++) { 1538 TSourceToken tk = gcurrentsqlstatement.sourcetokenlist.get(k); 1539 if (tk.tokencode == TBaseType.rrw_begin) { 1540 TSourceToken tkNext = tk.nextSolidToken(); 1541 if (tkNext == null || (tkNext.tokencode != TBaseType.rrw_sqlserver_tran 1542 && tkNext.tokencode != TBaseType.rrw_sqlserver_transaction 1543 && tkNext.tokencode != TBaseType.rrw_sqlserver_distributed 1544 && tkNext.tokencode != TBaseType.rrw_sqlserver_dialog 1545 && tkNext.tokencode != TBaseType.rrw_sqlserver_conversation)) { 1546 blockDepth++; 1547 } 1548 } else if (tk.tokencode == TBaseType.rrw_end) { 1549 TSourceToken tkNext = tk.nextSolidToken(); 1550 if (tkNext == null || tkNext.tokencode != TBaseType.rrw_sqlserver_conversation) { 1551 blockDepth--; 1552 } 1553 } 1554 } 1555 // blockDepth == 0 means all BEGIN/END pairs are balanced 1556 // so the END; is at the top level (proc boundary) 1557 if (blockDepth == 0) { 1558 readForNewStmt = true; 1559 } 1560 } 1561 } 1562 1563 if (readForNewStmt) { 1564 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1565 gcurrentsqlstatement = lcnextsqlstatement; 1566 gcurrentsqlstatement.sourcetokenlist.add(ast); 1567 gst = EFindSqlStateType.stsql; 1568 } else { 1569 lcnextsqlstatement = null; 1570 } 1571 break; 1572 } 1573 case sstmssqlDropSecurityPolicy: 1574 case sstmssqlAlterSecurityPolicy: 1575 case sstmssqlCreateSecurityPolicy: 1576 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1577 gcurrentsqlstatement = lcnextsqlstatement; 1578 gcurrentsqlstatement.sourcetokenlist.add(ast); 1579 gst = EFindSqlStateType.stsql; 1580 1581 break; 1582 default: { 1583 lcnextsqlstatement = null; 1584 break; 1585 } 1586 } 1587 } 1588 1589 if (gst == EFindSqlStateType.ststoredprocedurebody) 1590 gcurrentsqlstatement.sourcetokenlist.add(ast); 1591 } 1592 break; 1593 } 1594 } 1595 1596 //last statement 1597 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 1598 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 1599 } 1600 1601 // Set results in builder 1602 builder.sqlStatements(this.sqlstatements); 1603 builder.errorCode(errorcount); 1604 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 1605 } 1606}