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 while (yychar > 0) { 604 605 if (asourcetoken.tokencode == TBaseType.rrw_openrowset) { 606 // openrowset(....) 607 lcinopenrowset = true; 608 lcnested = 0; 609 } else if (asourcetoken.tokentype == ETokenType.ttleftparenthesis) { 610 if ((lcsteps > 0) && TBaseType.assigned(lcprevtoken)) { 611 if (lcprevtoken.tokencode == TBaseType.rrw_primary) { 612 lcprevtoken.tokencode = TBaseType.rrw_select - 2; //rw_primary2 613 checkconstarinttoken(lcprevtoken); 614 } else if (lcprevtoken.tokencode == TBaseType.rrw_foreign) { 615 lcprevtoken.tokencode = TBaseType.rrw_select - 4; //rw_foreign2 616 checkconstarinttoken(lcprevtoken); 617 } else if (lcprevtoken.tokencode == TBaseType.rrw_unique) { 618 lcprevtoken.tokencode = TBaseType.rrw_select - 1; //rw_unique2 619 checkconstarinttoken(lcprevtoken); 620 } 621 lcprevtoken = null; 622 lcsteps = 0; 623 } 624 625 // openrowset(....) 626 if (lcinopenrowset) 627 lcnested++; 628 } else if (asourcetoken.tokentype == ETokenType.ttrightparenthesis) { 629 // openrowset(....) 630 if (lcinopenrowset) { 631 if ((lcnested > 0)) 632 lcnested--; 633 if (lcnested == 0) 634 lcinopenrowset = false; 635 } 636 } else if (asourcetoken.tokentype == ETokenType.ttsemicolon) { 637 if (lcinopenrowset) { 638 asourcetoken.tokentype = ETokenType.ttsemicolon2; 639 } else { 640 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_begin, -1); 641 if (lctoken2 != null) { 642 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 643 asourcetoken.tokentype = ETokenType.ttsemicolon3; 644 } else { 645 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_try, -1); 646 if (lctoken2 == null) { 647 lctoken2 = asourcetoken.searchToken(TBaseType.rrw_catch, -1); 648 } 649 if (lctoken2 != null) { 650 lctoken3 = asourcetoken.searchToken(TBaseType.rrw_begin, -2); 651 if (lctoken3 != null) { 652 asourcetoken.tokencode = TBaseType.SEMI_COLON_AFTER_BEGIN; 653 asourcetoken.tokentype = ETokenType.ttsemicolon3; 654 } 655 } 656 } 657 } 658 659 lctoken = getprevtoken(asourcetoken); 660 if ((lctoken != null)) { 661 if (lctoken.tokentype == ETokenType.ttsemicolon) { 662 // treat this semicolon as a whitespace 663 asourcetoken.tokencode = TBaseType.lexspace; 664 } 665 } 666 } else if (asourcetoken.tokentype == ETokenType.ttperiod) { 667 lctoken = getprevtoken(asourcetoken); 668 if (TBaseType.assigned(lctoken)) { 669 // go.fieldname, go is a table alias, not a go statement 670 if (lctoken.tokencode == TBaseType.rrw_go) { 671 lctoken.tokencode = TBaseType.ident; 672 lctoken.tokentype = ETokenType.ttidentifier; 673 } 674 } 675 } else if (asourcetoken.tokencode == TBaseType.rrw_table) { 676 lctoken = getprevtoken(asourcetoken); 677 if (TBaseType.assigned(lctoken)) { 678 if (lctoken.tokencode == TBaseType.rrw_lock) { 679 lctoken.tokencode = TBaseType.rw_locktable; 680 } 681 } 682 } else if (asourcetoken.tokencode == TBaseType.rrw_into) { 683 lctoken = getprevtoken(asourcetoken); 684 if (TBaseType.assigned(lctoken)) { 685 if (lctoken.tokencode == TBaseType.rrw_sqlserver_copy) { 686 lctoken.tokencode = TBaseType.rrw_sqlserver_copyinto; 687 } 688 } 689 } else if (asourcetoken.tokencode == TBaseType.rrw_sqlserver_column) { 690 lctoken = getprevtoken(asourcetoken); 691 if (TBaseType.assigned(lctoken)) { 692 if (lctoken.tokencode == TBaseType.rrw_drop) { 693 asourcetoken.tokencode = TBaseType.rrw_sqlserver_drop_column; 694 } 695 } 696 } else if (asourcetoken.tokencode == TBaseType.rrw_go) { 697 iskeywordgo = true; 698 lctoken = getprevtoken(asourcetoken); 699 if (TBaseType.assigned(lctoken)) { 700 // go should not at same line as other sql statement 701 if ((lctoken.lineNo == asourcetoken.lineNo) && (lctoken.tokencode != ';')) { 702 iskeywordgo = false; 703 } 704 } 705 706 if (iskeywordgo) { 707 lcinopenrowset = false; 708 lcnested = 0; 709 lcprevtoken = asourcetoken; 710 } else { 711 asourcetoken.tokencode = TBaseType.ident; 712 asourcetoken.tokentype = ETokenType.ttidentifier; 713 } 714 } else if (asourcetoken.tokencode == TBaseType.rrw_primary) { 715 // primary key [clustered | nonclustered ] [hash] ( column list) 716 lcsteps = 2; 717 lcprevtoken = asourcetoken; 718 } else if (asourcetoken.tokencode == TBaseType.rrw_foreign) { 719 // foreign key [clustered | nonclustered ] ( column list) 720 lcsteps = 2; 721 lcprevtoken = asourcetoken; 722 } else if (asourcetoken.tokencode == TBaseType.rrw_unique) { 723 // unique [clustered | nonclustered ] [hash] ( column list) 724 lcsteps = 1; 725 lcprevtoken = asourcetoken; 726 } else if (asourcetoken.issolidtoken()) { 727 if (lcsteps > 0) { 728 if (!(TBaseType.mysametext("clustered", asourcetoken.toString()) 729 || TBaseType.mysametext("nonclustered", asourcetoken.toString()) 730 || TBaseType.mysametext("hash", asourcetoken.toString()) 731 )) 732 lcsteps--; 733 } 734 } 735 736 sourcetokenlist.add(asourcetoken); 737 738 asourcetoken = getanewsourcetoken(); 739 if (asourcetoken == null) break; 740 yychar = asourcetoken.tokencode; 741 } 742 } 743 744 /** 745 * Get previous token in token list. 746 * <p> 747 * Matches TGSqlParser.getprevtoken() implementation exactly to ensure 748 * consistent tokenization behavior. 749 */ 750 private TSourceToken getprevtoken(TSourceToken ptoken) { 751 TSourceTokenList lcstlist = ptoken.container; 752 if (TBaseType.assigned(lcstlist)) { 753 if ((ptoken.posinlist > 0) && (lcstlist.size() > ptoken.posinlist - 1)) { 754 if (!((lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttwhitespace) 755 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttreturn) 756 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttsimplecomment) 757 || (lcstlist.get(ptoken.posinlist - 1).tokentype == ETokenType.ttbracketedcomment) 758 )) { 759 return lcstlist.get(ptoken.posinlist - 1); 760 } else { 761 return lcstlist.nextsolidtoken(ptoken.posinlist - 1, -1, false); 762 } 763 } 764 } 765 return null; 766 } 767 768 /** 769 * Check and adjust constraint token codes. 770 * <p> 771 * When PRIMARY KEY, FOREIGN KEY, or UNIQUE is followed by '(', we need to 772 * look back to see if there's a CONSTRAINT keyword. If found, change its 773 * token code from rrw_constraint (515) to rw_constraint2 (298). 774 * <p> 775 * Extracted from: TGSqlParser.checkconstarinttoken() (lines 1989-2005) 776 * 777 * @param token The token after which to search (PRIMARY/FOREIGN/UNIQUE) 778 */ 779 private void checkconstarinttoken(TSourceToken token) { 780 TSourceTokenList lcStList = token.container; 781 if (TBaseType.assigned(lcStList)) { 782 // Look back 2 solid tokens to find CONSTRAINT keyword 783 TSourceToken lcPPToken = lcStList.nextsolidtoken(token.posinlist, -2, false); 784 if (TBaseType.assigned(lcPPToken)) { 785 if (lcPPToken.tokencode == flexer.getkeywordvalue("constraint")) { 786 // Change CONSTRAINT to special constraint2 token code 787 lcPPToken.tokencode = TBaseType.rw_constraint2; 788 } 789 } 790 } 791 } 792 793 // ========== SQL Server-Specific Raw Statement Extraction ========== 794 795 /** 796 * SQL Server-specific raw statement extraction logic. 797 * <p> 798 * Extracted from: TGSqlParser.domssqlgetrawsqlstatements() (lines 13081-13904) 799 */ 800 private void domssqlgetrawsqlstatements(SqlParseResult.Builder builder) { 801 int errorcount = 0; 802 int case_end_nest = 0; 803 804 if (TBaseType.assigned(sqlstatements)) sqlstatements.clear(); 805 if (!TBaseType.assigned(sourcetokenlist)) { 806 builder.errorCode(-1); 807 builder.errorMessage("Source token list is null"); 808 return; 809 } 810 811 gcurrentsqlstatement = null; 812 EFindSqlStateType gst = EFindSqlStateType.stnormal; 813 int lcblocklevel = 0; 814 int lctrycatchlevel = 0; 815 TSourceToken lcprevsolidtoken = null, lcnextsolidtoken, lcnnextsolidtoken; 816 TSourceToken ast = null; 817 int i, lcMergeInSelectNested = 0; 818 boolean lcisendconversation, lcstillinsql, lcMergeInSelect = false; 819 820 for (i = 0; i < sourcetokenlist.size(); i++) { 821 822 if ((ast != null) && (ast.issolidtoken())) 823 lcprevsolidtoken = ast; 824 825 ast = sourcetokenlist.get(i); 826 sourcetokenlist.curpos = i; 827 828 if (ast.tokencode == TBaseType.rrw_for) { 829 TSourceToken st1 = ast.nextSolidToken(); 830 if ((st1 != null) && (st1.tokencode == TBaseType.rrw_system_time)) { 831 ast.tokencode = TBaseType.rw_for_system_time; 832 } else if ((st1 != null) && TBaseType.mycomparetext(st1.getAstext(), "PATH") == 0) { 833 ast.tokencode = TBaseType.rw_for_path; 834 } 835 } 836 837 if (lcMergeInSelect) { 838 if (ast.tokencode == '(') lcMergeInSelectNested++; 839 if (ast.tokencode == ')') { 840 lcMergeInSelectNested--; 841 if (lcMergeInSelectNested == 0) { 842 lcMergeInSelect = false; 843 } 844 } 845 gcurrentsqlstatement.sourcetokenlist.add(ast); 846 continue; 847 } 848 849 if (ast.tokenstatus == ETokenStatus.tsignoredbygetrawstatement) { 850 gcurrentsqlstatement.sourcetokenlist.add(ast); 851 continue; 852 } 853 854 if (ast.tokencode == TBaseType.rrw_minus) { 855 TSourceToken st1 = ast.searchToken('(', 1); 856 if (st1 == null) { 857 st1 = ast.searchToken(TBaseType.rrw_select, 1); 858 if (st1 == null) { 859 ast.tokencode = TBaseType.ident; 860 } 861 } 862 } else if (ast.tokencode == TBaseType.rrw_merge) { 863 TSourceToken st1 = ast.nextSolidToken(); 864 if (st1.tokencode == TBaseType.rrw_join) { 865 ast.tokencode = TBaseType.rrw_merge2_sqlserver; 866 } 867 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '(')) { 868 lcMergeInSelect = true; 869 lcMergeInSelectNested++; 870 gcurrentsqlstatement.sourcetokenlist.add(ast); 871 continue; 872 } 873 } else if (ast.tokencode == TBaseType.rrw_sqlserver_value) { 874 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 875 TSourceToken st1 = ast.searchToken('(', 1); 876 if (st1 != null) { 877 ast.tokencode = TBaseType.rrw_xml_value; 878 } 879 } 880 } else if (ast.tokencode == TBaseType.rrw_sqlserver_modify) { 881 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 882 TSourceToken st1 = ast.searchToken('(', 1); 883 if (st1 != null) { 884 ast.tokencode = TBaseType.rrw_xml_modify; 885 } 886 } 887 } else if (ast.tokencode == TBaseType.rrw_sqlserver_query) { 888 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 889 TSourceToken st1 = ast.searchToken('(', 1); 890 if (st1 != null) { 891 ast.tokencode = TBaseType.rrw_xml_query; 892 } 893 } 894 } else if (ast.tokencode == TBaseType.rrw_sqlserver_exist) { 895 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 896 TSourceToken st1 = ast.searchToken('(', 1); 897 if (st1 != null) { 898 ast.tokencode = TBaseType.rrw_xml_exist; 899 } 900 } 901 } else if (ast.tokencode == TBaseType.rrw_sqlserver_nodes) { 902 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 903 TSourceToken st1 = ast.searchToken('(', 1); 904 if (st1 != null) { 905 ast.tokencode = TBaseType.rrw_xml_nodes; 906 } 907 } 908 } else if (ast.tokencode == TBaseType.ident && ast.tokentype == ETokenType.ttdbstring) { 909 // Handle bracket-quoted XML method names: .[value](), .[nodes](), etc. 910 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == '.')) { 911 TSourceToken st1 = ast.searchToken('(', 1); 912 if (st1 != null) { 913 String text = ast.toString(); 914 if (text.length() > 2) { 915 String inner = text.substring(1, text.length() - 1).toLowerCase(); 916 if (inner.equals("value")) { 917 ast.tokencode = TBaseType.rrw_xml_value; 918 } else if (inner.equals("modify")) { 919 ast.tokencode = TBaseType.rrw_xml_modify; 920 } else if (inner.equals("query")) { 921 ast.tokencode = TBaseType.rrw_xml_query; 922 } else if (inner.equals("exist")) { 923 ast.tokencode = TBaseType.rrw_xml_exist; 924 } else if (inner.equals("nodes")) { 925 ast.tokencode = TBaseType.rrw_xml_nodes; 926 } 927 } 928 } 929 } 930 } else if (ast.tokencode == TBaseType.rrw_check) { 931 if ((lcprevsolidtoken != null) && (lcprevsolidtoken.tokencode == TBaseType.rrw_with)) { 932 lcprevsolidtoken.tokencode = TBaseType.rrw_sqlserver_check_with; 933 } 934 } else if (ast.tokencode == TBaseType.rrw_sqlserver_next) { 935 TSourceToken st1 = ast.nextSolidToken(); 936 if ((st1 != null) && (st1.tokencode == '.')) { 937 ast.tokencode = TBaseType.ident; 938 } 939 } else if (ast.tokencode == TBaseType.rrw_fetch) { 940 if ((lcprevsolidtoken != null) && ((lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_row) || (lcprevsolidtoken.tokencode == TBaseType.rrw_sqlserver_rows))) { 941 TSourceToken prev2 = lcprevsolidtoken.searchToken(TBaseType.rrw_open, -1); 942 if (prev2 == null) { 943 ast.tokencode = TBaseType.rrw_sqlserver_offset_fetch; 944 } 945 } 946 } else if ((ast.tokencode == TBaseType.rrw_exec) || (ast.tokencode == TBaseType.rrw_execute)) { 947 // search ;2 after execute 948 // EXECUTE ApxSQL_Test_Triggers_Add;2 @TableName, @AddFlag 949 int searchRange = 4; 950 TSourceToken endTokenInSameLine = ast.searchTokenAtTheEndOfSameLine(); 951 if (endTokenInSameLine != null) { 952 searchRange = endTokenInSameLine.posinlist - ast.posinlist; 953 } 954 TSourceToken st1 = ast.searchToken(';', searchRange); 955 if (st1 != null) { 956 TSourceToken numSt = st1.nextSolidToken(); 957 if ((numSt != null) && (numSt.tokentype == ETokenType.ttnumber)) { 958 st1.tokencode = TBaseType.rrw_sqlserver_semicolon_module_number; 959 st1.tokentype = ETokenType.ttidentifier; 960 } 961 } 962 } else if (ast.tokencode == TBaseType.rrw_sqlserver_trim) { 963 TSourceToken st1 = ast.nextSolidToken(); 964 if (st1 != null) { 965 if (st1.tokencode == '(') { 966 // keep as trim keyword 967 } else { 968 ast.tokencode = TBaseType.ident; 969 } 970 } 971 } 972 973 if (vendor == EDbVendor.dbvopenedge){ 974 if (ast.tokencode == TBaseType.rrw_order){ 975 TSourceToken st1 = ast.searchToken(TBaseType.rrw_by,1); 976 if (st1 == null) { 977 ast.tokencode = TBaseType.ident; 978 } 979 }else if (ast.tokencode == TBaseType.rrw_with){ 980 TSourceToken st1 = ast.searchToken(TBaseType.rrw_check,1); 981 if (st1 != null) { 982 ast.tokencode = TBaseType.rrw_openedge_with_check; 983 } 984 } 985 } 986 987 if (gst == EFindSqlStateType.ststoredprocedurebody) { 988 if (!((ast.tokencode == TBaseType.rrw_go) 989 || (ast.tokencode == TBaseType.rrw_create) 990 || (ast.tokencode == TBaseType.rrw_alter))) { 991 gcurrentsqlstatement.sourcetokenlist.add(ast); 992 continue; 993 } 994 } 995 996 TCustomSqlStatement lcnextsqlstatement = sqlcmds.issql(ast, gst, gcurrentsqlstatement); 997 998 switch (gst) { 999 case sterror: { 1000 if (TBaseType.assigned(lcnextsqlstatement)) { 1001 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1002 gcurrentsqlstatement = lcnextsqlstatement; 1003 gcurrentsqlstatement.sourcetokenlist.add(ast); 1004 gst = EFindSqlStateType.stsql; 1005 } else if ((ast.tokentype == ETokenType.ttsemicolon)) { 1006 gcurrentsqlstatement.sourcetokenlist.add(ast); 1007 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1008 gst = EFindSqlStateType.stnormal; 1009 } else { 1010 gcurrentsqlstatement.sourcetokenlist.add(ast); 1011 } 1012 break; 1013 } 1014 case stnormal: { 1015 if ((ast.tokencode == TBaseType.cmtdoublehyphen) 1016 || (ast.tokencode == TBaseType.cmtslashstar) 1017 || (ast.tokencode == TBaseType.lexspace) 1018 || (ast.tokencode == TBaseType.lexnewline) 1019 || (ast.tokentype == ETokenType.ttsemicolon)) { 1020 if (TBaseType.assigned(gcurrentsqlstatement)) { 1021 gcurrentsqlstatement.sourcetokenlist.add(ast); 1022 } 1023 1024 if (TBaseType.assigned(lcprevsolidtoken) && (ast.tokentype == ETokenType.ttsemicolon)) { 1025 if (lcprevsolidtoken.tokentype == ETokenType.ttsemicolon) { 1026 // ;;;; continuous semicolon,treat it as comment 1027 ast.tokentype = ETokenType.ttsimplecomment; 1028 ast.tokencode = TBaseType.cmtdoublehyphen; 1029 } 1030 } 1031 1032 continue; 1033 } 1034 1035 gcurrentsqlstatement = lcnextsqlstatement; 1036 1037 if (TBaseType.assigned(gcurrentsqlstatement)) { 1038 switch (gcurrentsqlstatement.sqlstatementtype) { 1039 case sstmssqlcreateprocedure: 1040 case sstmssqlcreatefunction: 1041 case sstcreatetrigger: 1042 case sstmssqlalterprocedure: 1043 case sstmssqlalterfunction: 1044 case sstmssqlaltertrigger: { 1045 gcurrentsqlstatement.sourcetokenlist.add(ast); 1046 gst = EFindSqlStateType.ststoredprocedure; 1047 break; 1048 } 1049 case sstmssqlbegintry: 1050 case sstmssqlbegincatch: { 1051 gcurrentsqlstatement.sourcetokenlist.add(ast); 1052 gst = EFindSqlStateType.sttrycatch; 1053 lctrycatchlevel = 0; 1054 break; 1055 } 1056 case sstmssqlgo: { 1057 gcurrentsqlstatement.sourcetokenlist.add(ast); 1058 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1059 gst = EFindSqlStateType.stnormal; 1060 break; 1061 } 1062 default: { 1063 gcurrentsqlstatement.sourcetokenlist.add(ast); 1064 gst = EFindSqlStateType.stsql; 1065 break; 1066 } 1067 } 1068 } else { 1069 if (ast.tokencode == TBaseType.rrw_begin) { 1070 gcurrentsqlstatement = new TMssqlBlock(vendor); 1071 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 1072 gcurrentsqlstatement.sourcetokenlist.add(ast); 1073 gst = EFindSqlStateType.stblock; 1074 } else { 1075 if (sqlstatements.size() == 0) { 1076 //first statement of mssql batch, treat it as exec sp 1077 gst = EFindSqlStateType.stsql; 1078 gcurrentsqlstatement = new TMssqlExecute(vendor); 1079 gcurrentsqlstatement.sourcetokenlist.add(ast); 1080 } else if (sqlstatements.get(sqlstatements.size() - 1).sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1081 // prev sql is go, treat it as exec sp 1082 gst = EFindSqlStateType.stsql; 1083 gcurrentsqlstatement = new TMssqlExecute(vendor); 1084 gcurrentsqlstatement.sourcetokenlist.add(ast); 1085 } 1086 } 1087 } 1088 1089 if (!TBaseType.assigned(gcurrentsqlstatement)) //error tokentext found 1090 { 1091 this.syntaxErrors.add(new TSyntaxError(ast.getAstext(), ast.lineNo, (ast.columnNo < 0 ? 0 : ast.columnNo) 1092 , "Error when tokenlize", EErrorType.spwarning, TBaseType.MSG_WARNING_ERROR_WHEN_TOKENIZE, null, ast.posinlist)); 1093 1094 ast.tokentype = ETokenType.tttokenlizererrortoken; 1095 gst = EFindSqlStateType.sterror; 1096 1097 gcurrentsqlstatement = new TUnknownSqlStatement(vendor); 1098 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstinvalid; 1099 gcurrentsqlstatement.sourcetokenlist.add(ast); 1100 } 1101 break; 1102 } 1103 case stblock: { 1104 if (TBaseType.assigned(lcnextsqlstatement)) { 1105 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1106 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1107 gcurrentsqlstatement = lcnextsqlstatement; 1108 gcurrentsqlstatement.sourcetokenlist.add(ast); 1109 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1110 gst = EFindSqlStateType.stnormal; 1111 } else { 1112 lcnextsqlstatement = null; 1113 } 1114 } 1115 1116 if (gst == EFindSqlStateType.stblock) { 1117 gcurrentsqlstatement.sourcetokenlist.add(ast); 1118 if (ast.tokencode == TBaseType.rrw_begin) { 1119 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 1120 if (TBaseType.assigned(lcnextsolidtoken)) { 1121 if (!((lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_tran) 1122 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_transaction) 1123 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_distributed) 1124 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_dialog) 1125 || (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation) 1126 )) 1127 lcblocklevel++; 1128 } else 1129 lcblocklevel++; 1130 1131 } else if (ast.tokencode == TBaseType.rrw_case) 1132 lcblocklevel++; 1133 else if (ast.tokencode == TBaseType.rrw_end) { 1134 1135 lcisendconversation = false; 1136 1137 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 1138 if (TBaseType.assigned(lcnextsolidtoken)) { 1139 if (lcnextsolidtoken.tokencode == TBaseType.rrw_sqlserver_conversation) 1140 lcisendconversation = true; 1141 } 1142 1143 if (!lcisendconversation) { 1144 1145 if (lcblocklevel == 0) { 1146 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1147 if (TBaseType.assigned(lcnextsolidtoken)) { 1148 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 1149 gst = EFindSqlStateType.stsql; 1150 } else if (lcnextsolidtoken.tokentype == ETokenType.ttsemicolon) { 1151 lcnnextsolidtoken = sourcetokenlist.nextsolidtoken(lcnextsolidtoken.posinlist, 1, false); 1152 if (TBaseType.assigned(lcnnextsolidtoken)) { 1153 if (lcnnextsolidtoken.tokencode == TBaseType.rrw_else) { 1154 gst = EFindSqlStateType.stsql; 1155 } 1156 } 1157 } 1158 } 1159 } 1160 1161 if (gst != EFindSqlStateType.stsql) { 1162 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1163 gst = EFindSqlStateType.stnormal; 1164 } 1165 1166 } else { 1167 lcblocklevel--; 1168 } 1169 1170 } 1171 } 1172 } 1173 break; 1174 } 1175 case sttrycatch: { 1176 1177 if (TBaseType.assigned(lcnextsqlstatement)) { 1178 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1179 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1180 gcurrentsqlstatement = lcnextsqlstatement; 1181 gcurrentsqlstatement.sourcetokenlist.add(ast); 1182 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1183 gst = EFindSqlStateType.stnormal; 1184 } else { 1185 if ( 1186 (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry) 1187 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 1188 ) 1189 lctrycatchlevel++; 1190 lcnextsqlstatement = null; 1191 } 1192 } 1193 1194 if (gst == EFindSqlStateType.sttrycatch) { 1195 gcurrentsqlstatement.sourcetokenlist.add(ast); 1196 if ((ast.tokencode == TBaseType.rrw_try) || 1197 (ast.tokencode == TBaseType.rrw_catch)) { 1198 if (TBaseType.assigned(lcprevsolidtoken)) { 1199 if (lcprevsolidtoken.tokencode == TBaseType.rrw_end) { 1200 if (lctrycatchlevel == 0) { 1201 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1202 gst = EFindSqlStateType.stnormal; 1203 } else 1204 lctrycatchlevel--; 1205 } 1206 } 1207 } 1208 } 1209 break; 1210 } 1211 case stprocedureWithReturn: { 1212 // found return statement in create procedure/function 1213 1214 if (TBaseType.assigned(lcnextsqlstatement)) { 1215 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1216 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1217 gcurrentsqlstatement = lcnextsqlstatement; 1218 gcurrentsqlstatement.sourcetokenlist.add(ast); 1219 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1220 gst = EFindSqlStateType.stnormal; 1221 break; 1222 } 1223 } 1224 1225 gcurrentsqlstatement.sourcetokenlist.add(ast); 1226 if ((gst == EFindSqlStateType.stprocedureWithReturn) && (ast.tokentype == ETokenType.ttsemicolon)) { 1227 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1228 gst = EFindSqlStateType.stnormal; 1229 } 1230 break; 1231 } 1232 case stsql: { 1233 if ((ast.tokentype == ETokenType.ttsemicolon)) { 1234 lcstillinsql = false; 1235 if (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1236 lcnextsolidtoken = sourcetokenlist.nextsolidtoken(i, 1, false); 1237 if (TBaseType.assigned(lcnextsolidtoken)) { 1238 if (lcnextsolidtoken.tokencode == TBaseType.rrw_else) { 1239 // if ( expr stmt; else 1240 gcurrentsqlstatement.sourcetokenlist.add(ast); 1241 lcstillinsql = true; 1242 } 1243 1244 } 1245 } 1246 1247 if (!lcstillinsql) { 1248 gst = EFindSqlStateType.stnormal; 1249 gcurrentsqlstatement.sourcetokenlist.add(ast); 1250 gcurrentsqlstatement.semicolonended = ast; 1251 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1252 } 1253 1254 } else if (TBaseType.assigned(lcnextsqlstatement)) { 1255 1256 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1257 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1258 gcurrentsqlstatement = lcnextsqlstatement; 1259 gcurrentsqlstatement.sourcetokenlist.add(ast); 1260 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1261 gst = EFindSqlStateType.stnormal; 1262 continue; 1263 } 1264 1265 // A typed TUnknownSqlStatement (admin DDL recognized by 1266 // the splitter, no deep AST) has no internal statement 1267 // structure: it runs to its semicolon. Without this, 1268 // ALTER EXTERNAL LANGUAGE ... SET (...) split at SET 1269 // because SET is a registered statement head. 1270 if ((gcurrentsqlstatement instanceof gudusoft.gsqlparser.stmt.TUnknownSqlStatement) 1271 && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstinvalid) 1272 && (gcurrentsqlstatement.sqlstatementtype != ESqlStatementType.sstunknown)) { 1273 gcurrentsqlstatement.sourcetokenlist.add(ast); 1274 continue; 1275 } 1276 1277 switch (gcurrentsqlstatement.sqlstatementtype) { 1278 case sstmssqlif: 1279 case sstmssqlwhile: { 1280 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegincatch) 1281 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlbegintry)) { 1282 gcurrentsqlstatement.sourcetokenlist.add(ast); 1283 gst = EFindSqlStateType.stblock; 1284 lcblocklevel = 1; 1285 lcnextsqlstatement = null; 1286 continue; 1287 1288 } else if (gcurrentsqlstatement.dummytag == 1) { 1289 // if ( cond ^stmt nextstmt (^ stands for current pos) 1290 gcurrentsqlstatement.sourcetokenlist.add(ast); 1291 1292 if ((lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1293 || (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) 1294 gcurrentsqlstatement.dummytag = 1; 1295 else 1296 gcurrentsqlstatement.dummytag = 0; 1297 1298 lcnextsqlstatement = null; 1299 continue; 1300 } else { 1301 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1302 if ((ast.nextSolidToken() != null) && (ast.nextSolidToken().tokencode == TBaseType.rrw_sqlserver_exists)) { 1303 gcurrentsqlstatement.sourcetokenlist.add(ast); 1304 lcnextsqlstatement = null; 1305 continue; 1306 } 1307 } 1308 } 1309 break; 1310 } 1311 case sstmssqlalterqueue: { 1312 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlexec) { 1313 // execute can't be used to delimite alter queue 1314 gcurrentsqlstatement.sourcetokenlist.add(ast); 1315 lcnextsqlstatement = null; 1316 continue; 1317 1318 } 1319 break; 1320 } 1321 case sstmssqlcreateschema: { 1322 gcurrentsqlstatement.sourcetokenlist.add(ast); 1323 lcnextsqlstatement = null; 1324 continue; 1325 } 1326 } 1327 1328 // "IF EXISTS <name>" belongs to the statement it 1329 // follows (DROP DEFAULT d IF EXISTS ...). But 1330 // "IF EXISTS (SELECT ...)" is control flow starting a 1331 // new statement - real scripts chain unterminated 1332 // statements before it - so the '(' after EXISTS 1333 // decides. 1334 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) { 1335 TSourceToken existsTok = ast.nextSolidToken(); 1336 if ((existsTok != null) 1337 && (existsTok.tokencode == TBaseType.rrw_sqlserver_exists)) { 1338 TSourceToken afterExists = existsTok.nextSolidToken(); 1339 if ((afterExists == null) 1340 || (afterExists.tokentype != ETokenType.ttleftparenthesis)) { 1341 gcurrentsqlstatement.sourcetokenlist.add(ast); 1342 continue; 1343 } 1344 } 1345 } 1346 1347 // ALTER PARTITION FUNCTION f() MERGE|SPLIT RANGE (...): 1348 // the MERGE keyword is the function operation, not a 1349 // MERGE statement. 1350 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlAlterPartitionFunction) 1351 && (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmerge)) { 1352 gcurrentsqlstatement.sourcetokenlist.add(ast); 1353 continue; 1354 } 1355 1356 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1357 gcurrentsqlstatement = lcnextsqlstatement; 1358 gcurrentsqlstatement.sourcetokenlist.add(ast); 1359 1360 switch (gcurrentsqlstatement.sqlstatementtype) { 1361 case sstmssqlcreateprocedure: 1362 case sstmssqlcreatefunction: 1363 case sstcreatetrigger: 1364 case sstmssqlalterprocedure: 1365 case sstmssqlalterfunction: 1366 case sstmssqlaltertrigger: { 1367 gst = EFindSqlStateType.ststoredprocedure; 1368 break; 1369 } 1370 case sstmssqlbegintry: 1371 case sstmssqlbegincatch: { 1372 gst = EFindSqlStateType.sttrycatch; 1373 lctrycatchlevel = 0; 1374 break; 1375 } 1376 case sstmssqlgo: { 1377 gst = EFindSqlStateType.stnormal; 1378 break; 1379 } 1380 default: { 1381 gst = EFindSqlStateType.stsql; 1382 break; 1383 } 1384 } 1385 1386 } else if ((ast.tokencode == TBaseType.rrw_begin)) { 1387 if ((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1388 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile)) { 1389 gst = EFindSqlStateType.stblock; 1390 lcblocklevel = 0; 1391 gcurrentsqlstatement.sourcetokenlist.add(ast); 1392 } else if (case_end_nest > 0) { 1393 // Inside a CASE expression — BEGIN cannot be a top-level block boundary 1394 gcurrentsqlstatement.sourcetokenlist.add(ast); 1395 } else { 1396 // mantisbt 4411: a plain BEGIN ... END after a complete statement 1397 // (EXEC, SELECT, UPDATE, SET, DECLARE, PRINT, RAISERROR, label, ...) 1398 // starts a new top-level TMssqlBlock. Previously the else branch 1399 // silently swallowed BEGIN into the preceding statement, breaking 1400 // the parse for batches like: 1401 // EXEC dbo.p1 1402 // BEGIN UPDATE x SET y = 1 END 1403 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1404 gcurrentsqlstatement = new TMssqlBlock(vendor); 1405 gcurrentsqlstatement.sqlstatementtype = ESqlStatementType.sstmssqlblock; 1406 gcurrentsqlstatement.sourcetokenlist.add(ast); 1407 gst = EFindSqlStateType.stblock; 1408 } 1409 } else if ((ast.tokencode == TBaseType.rrw_case)) { 1410 case_end_nest++; 1411 gcurrentsqlstatement.sourcetokenlist.add(ast); 1412 } else if ((ast.tokencode == TBaseType.rrw_end)) { 1413 if (case_end_nest > 0) { 1414 case_end_nest--; 1415 } 1416 gcurrentsqlstatement.sourcetokenlist.add(ast); 1417 } else if ((ast.tokencode == TBaseType.rrw_else)) { 1418 gcurrentsqlstatement.sourcetokenlist.add(ast); 1419 if (((gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlif) 1420 || (gcurrentsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlwhile) 1421 ) && (case_end_nest == 0)) 1422 gcurrentsqlstatement.dummytag = 1; 1423 } else { 1424 gcurrentsqlstatement.sourcetokenlist.add(ast); 1425 } 1426 break; 1427 } 1428 case ststoredprocedure: { 1429 if (TBaseType.assigned(lcnextsqlstatement)) { 1430 if (lcnextsqlstatement.sqlstatementtype == ESqlStatementType.sstmssqlgo) { 1431 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1432 gcurrentsqlstatement = lcnextsqlstatement; 1433 gcurrentsqlstatement.sourcetokenlist.add(ast); 1434 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1435 gst = EFindSqlStateType.stnormal; 1436 } else if (lcnextsqlstatement.sqlstatementtype == sstmssqlreturn) { 1437 gst = EFindSqlStateType.stprocedureWithReturn; 1438 gcurrentsqlstatement.sourcetokenlist.add(ast); 1439 lcnextsqlstatement = null; 1440 } else { 1441 gst = EFindSqlStateType.ststoredprocedurebody; 1442 gcurrentsqlstatement.sourcetokenlist.add(ast); 1443 1444 lcnextsqlstatement = null; 1445 } 1446 } 1447 1448 if (gst == EFindSqlStateType.ststoredprocedure) { 1449 gcurrentsqlstatement.sourcetokenlist.add(ast); 1450 if (ast.tokencode == TBaseType.rrw_begin) { 1451 gst = EFindSqlStateType.stblock; 1452 } else if ((ast.tokentype == ETokenType.ttsemicolon) 1453 && hasExternalNameBody(gcurrentsqlstatement)) { 1454 // CLR routine: AS EXTERNAL NAME assembly.class.method has no 1455 // BEGIN...END body, so the semicolon ends the statement. Without 1456 // this the splitter keeps waiting for a body and swallows the 1457 // next statement of the script. 1458 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1459 gst = EFindSqlStateType.stnormal; 1460 } 1461 } 1462 break; 1463 } 1464 case ststoredprocedurebody: { 1465 if (TBaseType.assigned(lcnextsqlstatement)) { 1466 switch (lcnextsqlstatement.sqlstatementtype) { 1467 case sstmssqlgo: { 1468 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1469 gcurrentsqlstatement = lcnextsqlstatement; 1470 gcurrentsqlstatement.sourcetokenlist.add(ast); 1471 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1472 gst = EFindSqlStateType.stnormal; 1473 break; 1474 } 1475 case sstmssqlcreateprocedure: 1476 case sstmssqlcreatefunction: 1477 case sstcreatetrigger: 1478 case sstmssqlalterprocedure: 1479 case sstmssqlalterfunction: 1480 case sstmssqlaltertrigger: { 1481 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1482 gcurrentsqlstatement = lcnextsqlstatement; 1483 gcurrentsqlstatement.sourcetokenlist.add(ast); 1484 gst = EFindSqlStateType.ststoredprocedure; 1485 break; 1486 } 1487 case sstcreateview: 1488 case sstcreatetable: { 1489 1490 boolean readForNewStmt = false; 1491 TSourceToken st1 = ast.searchToken(';', -1); 1492 if (st1 != null) { 1493 TSourceToken st2 = ast.searchToken(TBaseType.rrw_end, -2); 1494 if (st2 != null) { 1495 // Verify this END is at block level 0 (not inside a nested 1496 // BEGIN...END or BEGIN TRY...END TRY block) by counting 1497 // BEGINs and ENDs in the current proc's token list. 1498 int blockDepth = 0; 1499 for (int k = 0; k < gcurrentsqlstatement.sourcetokenlist.size(); k++) { 1500 TSourceToken tk = gcurrentsqlstatement.sourcetokenlist.get(k); 1501 if (tk.tokencode == TBaseType.rrw_begin) { 1502 TSourceToken tkNext = tk.nextSolidToken(); 1503 if (tkNext == null || (tkNext.tokencode != TBaseType.rrw_sqlserver_tran 1504 && tkNext.tokencode != TBaseType.rrw_sqlserver_transaction 1505 && tkNext.tokencode != TBaseType.rrw_sqlserver_distributed 1506 && tkNext.tokencode != TBaseType.rrw_sqlserver_dialog 1507 && tkNext.tokencode != TBaseType.rrw_sqlserver_conversation)) { 1508 blockDepth++; 1509 } 1510 } else if (tk.tokencode == TBaseType.rrw_end) { 1511 TSourceToken tkNext = tk.nextSolidToken(); 1512 if (tkNext == null || tkNext.tokencode != TBaseType.rrw_sqlserver_conversation) { 1513 blockDepth--; 1514 } 1515 } 1516 } 1517 // blockDepth == 0 means all BEGIN/END pairs are balanced 1518 // so the END; is at the top level (proc boundary) 1519 if (blockDepth == 0) { 1520 readForNewStmt = true; 1521 } 1522 } 1523 } 1524 1525 if (readForNewStmt) { 1526 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1527 gcurrentsqlstatement = lcnextsqlstatement; 1528 gcurrentsqlstatement.sourcetokenlist.add(ast); 1529 gst = EFindSqlStateType.stsql; 1530 } else { 1531 lcnextsqlstatement = null; 1532 } 1533 break; 1534 } 1535 case sstmssqlDropSecurityPolicy: 1536 case sstmssqlAlterSecurityPolicy: 1537 case sstmssqlCreateSecurityPolicy: 1538 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, false, builder); 1539 gcurrentsqlstatement = lcnextsqlstatement; 1540 gcurrentsqlstatement.sourcetokenlist.add(ast); 1541 gst = EFindSqlStateType.stsql; 1542 1543 break; 1544 default: { 1545 lcnextsqlstatement = null; 1546 break; 1547 } 1548 } 1549 } 1550 1551 if (gst == EFindSqlStateType.ststoredprocedurebody) 1552 gcurrentsqlstatement.sourcetokenlist.add(ast); 1553 } 1554 break; 1555 } 1556 } 1557 1558 //last statement 1559 if (TBaseType.assigned(gcurrentsqlstatement) && (gst != EFindSqlStateType.stnormal)) { 1560 onRawStatementComplete(parserContext, gcurrentsqlstatement, fparser, null, sqlstatements, true, builder); 1561 } 1562 1563 // Set results in builder 1564 builder.sqlStatements(this.sqlstatements); 1565 builder.errorCode(errorcount); 1566 builder.errorMessage(errorcount == 0 ? "" : String.format("Extraction completed with %d error(s)", errorcount)); 1567 } 1568}