001package gudusoft.gsqlparser.stmt; 002 003 004import gudusoft.gsqlparser.*; 005import gudusoft.gsqlparser.compiler.TFrame; 006import gudusoft.gsqlparser.compiler.TSymbolTableManager; 007import gudusoft.gsqlparser.compiler.TVariable; 008import gudusoft.gsqlparser.nodes.*; 009import gudusoft.gsqlparser.nodes.mssql.TProcedureOption; 010 011/** 012 * Create procedure statement. 013 * 存储过程如果是 $$ body $$ 形式的,有两种解析方法, 014 * 方法1,把 $$ body $$ 中的 body 在 lexical 阶段即分解为独立的 token, 整个 create procedure statement 在 parser 中一次性完成解析 015 * 采用这个方法的数据库: greenplum, postgres, redshift, snowflake 016 * 017 * GSP Java version 2.8.9.3(2024-5-27) [Snowflake] support string literal delimiter $ or ' around procedure definition. 018 * '' 中的存储过程代码被 {@link TGSqlParser.expandDollarString()} 方法处理,处理后的代码被解析。 019 * 020 * 2024/6/22, snowflake create procedure 的 body 如果是采用 $$ body $$, 在 lexer 中分解为独立的 token 021 * 022 * 方法2,把 $$ body $$ 作为一个整体字符串看待,需要进行二次解析, 都是调用 postgres parser 进行的分析。采用这种方法是,body中用到的 select 023 * 等语句用的是 postgres的语法分析,而不是原数据库 select 语句的语法分析,这可能导致不正确的解析结果。 024 * 这种方法为历史遗留问题,需要都改为方法1。目前采用这种方法的数据库有: 025 * netezza, 026 * vertica 027 * 028 * Supported databases 029 * <ul> 030 * <li>Postgres</li> 031 *<li>redshift</li> 032 * </ul> 033 */ 034public class TCreateProcedureStmt extends TRoutine{ 035 036 private TObjectName functionName = null; 037 private TConstant objfile; 038 private TConstant linkSymbol; 039 private TConstant procedureLanguage; 040 private TTableElementList returnTableDefinitions = null; 041 042 /** 043 * Returns the column definitions from the RETURNS TABLE(...) clause. 044 * For example, for {@code RETURNS TABLE(id INT, name VARCHAR)}, this returns 045 * a list containing column definitions for "id INT" and "name VARCHAR". 046 * Returns null if the procedure does not use RETURNS TABLE or has an empty TABLE(). 047 * <p>Currently supported for Snowflake.</p> 048 * @return the column definitions list, or null 049 */ 050 public TTableElementList getReturnTableDefinitions() { 051 return returnTableDefinitions; 052 } 053 054 public TConstant getProcedureLanguage() { 055 return procedureLanguage; 056 } 057 058 public TConstant getLinkSymbol() { 059 return linkSymbol; 060 } 061 public TConstant getObjfile() { 062 return objfile; 063 } 064 065 private TPTNodeList <TProcedureOption> procedureOptions; 066 private boolean isForReplication; 067 068 public void setProcedureOptions(TPTNodeList<TProcedureOption> procedureOptions) { 069 this.procedureOptions = procedureOptions; 070 } 071 072 public TPTNodeList<TProcedureOption> getProcedureOptions() { 073 074 return procedureOptions; 075 } 076 077 public void setForReplication(boolean isForReplication) { 078 this.isForReplication = isForReplication; 079 } 080 081 public boolean isForReplication() { 082 return isForReplication; 083 } 084 085 public TCreateProcedureStmt (EDbVendor dbvendor){ 086 super(dbvendor); 087 sqlstatementtype = ESqlStatementType.sstcreateprocedure ; 088 } 089 090 @Override 091 public TObjectName getStoredProcedureName(){ 092 return procedureName; 093 } 094 095 /** 096 * The name that you give to the procedure that you are declaring or defining. 097 * @return 098 */ 099 public TObjectName getProcedureName() { 100 return procedureName; 101 } 102 103 private TObjectName procedureName = null; 104 105 106 107 public int doParseStatement(TCustomSqlStatement psql) { 108 if (rootNode == null) return -1; 109 // rootNode may be a stale node left over from a previous statement parsed by 110 // the shared vendor parser when this statement failed checksyntax (yyparse does 111 // not reset rootNode on failure). The Sybase re-parse path in 112 // TCustomSqlStatement.parsestatement() calls doParseStatement() whenever 113 // rootNode != null, so guard the cast to avoid a ClassCastException (Mantis 4453). 114 // Mirrors the same guard already used by the Informix/Teradata create-procedure stmts. 115 if (!(rootNode instanceof TCreateProcedureSqlNode)) return -1; 116 TCreateProcedureSqlNode createProcedureNode = (TCreateProcedureSqlNode)rootNode; 117 118 super.doParseStatement(psql); 119 TFrame currentFrame = new TFrame(this.stmtScope); 120 currentFrame.pushMeToStack(getFrameStack()); 121 122 procedureName = createProcedureNode.getProcedureName(); 123 124 if (getSqlEnv() != null) { 125 getSqlEnv().addProcedure(procedureName.toString(),true); 126 127 // move to TDatabaseObjectResolver 128 129// if (getSqlEnv().getDefaultCatalogName() != null){ 130// if (procedureName.getDatabaseToken() == null){ 131// procedureName.setDatabaseToken(new TSourceToken(getSqlEnv().getDefaultCatalogName())); 132// } 133// } 134 } 135 136 setRoutineName(procedureName); 137 138 procedureLanguage = createProcedureNode.getProcedureLanguage(); 139 if (procedureLanguage != null){ 140 if (getRoutineLanguage() == null){ 141 // not already set during TGsqlParser.dopostgresqlgetrawsqlstatements() 142 // then we set it here 143 setRoutineLanguage(procedureLanguage.toString()); 144 } 145 setRoutineLanguageInConstant(procedureLanguage); 146 }else { 147 148 if (dbvendor == EDbVendor.dbvvertica){ 149 // vertica, set default to plvsql 150 TConstant c = new TConstant(); 151 c.init(ELiteralType.etString, new TSourceToken("plvsql")); 152 procedureLanguage = c; 153 setRoutineLanguageInConstant(procedureLanguage); 154 setRoutineLanguage("plvsql"); 155 } 156 157 } 158 this.setLabelName(createProcedureNode.getLabelName()); 159 objfile = createProcedureNode.getObjfile(); 160 linkSymbol = createProcedureNode.getLinkSymbol(); 161 this.setReturnDataType(createProcedureNode.getReturnDataType()); 162 // Extract RETURNS TABLE column definitions (Snowflake) 163 if (createProcedureNode.getReturnTable() != null) { 164 TDummy dmy = createProcedureNode.getReturnTable(); 165 if (dmy.list1 instanceof TTableElementList) { 166 this.returnTableDefinitions = (TTableElementList) dmy.list1; 167 this.returnTableDefinitions.doParse(this, ESqlClause.unknown); 168 } 169 } 170 if (createProcedureNode.getFunctionBody() != null){ 171 setRoutineBodyInConstant(createProcedureNode.getFunctionBody()); 172 setRoutineBody(createProcedureNode.getFunctionBody().toString()); 173 } 174 175 this.setParameterDeclarations( createProcedureNode.getParameters()); 176 // sql server 177 procedureOptions = createProcedureNode.getProcedureOptions(); 178 isForReplication = createProcedureNode.isForReplication(); 179 this.setReturnDataType(createProcedureNode.getReturnDataType()); 180 // end sql server 181 182 // push parameterDeclarations into symbolTable 183 if (this.getParameterDeclarations() != null){ 184 for(int i=0;i< this.getParameterDeclarations().size();i++){ 185 this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjParameter,this, this.getParameterDeclarations().getParameterDeclarationItem(i))); 186 TParameterDeclaration parameterDeclaration = this.getParameterDeclarations().getParameterDeclarationItem(i); 187 if (parameterDeclaration.getParameterName() != null){ 188 TVariable variable = new TVariable(parameterDeclaration.getParameterName(),parameterDeclaration,procedureName); 189 190 TVariable variableOfThisParam = TSymbolTableManager.searchSymbolVariable(this.getFrameStack(),procedureName+".$"+i); 191 if (variableOfThisParam != null){ 192 variable.setVariableStr(variableOfThisParam.getVariableStr()); 193 } 194 195 // variable.variableStr = TBaseType.getStringInsideLiteral(getSqlEnv().getProcedureParameterValue(procedureName.toString(), i)); 196 this.stmtScope.addSymbol(variable); 197 } 198 } 199 } 200 201 202 switch (dbvendor){ 203 case dbvdb2: 204 TCompoundSqlNode compoundSqlNode = createProcedureNode.getCompoundSqls(); 205 if (compoundSqlNode != null){ 206 if (compoundSqlNode.getDeclareStmts() != null){ 207 compoundSqlNode.getDeclareStmts().doParse(this,ESqlClause.unknown); 208 209 // push variable declare into symbolTable, and add to declareStatements 210 for(int i=0;i<compoundSqlNode.getDeclareStmts().size();i++){ 211 this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjVariable,this,compoundSqlNode.getDeclareStmts().getStatementSqlNode(i).getStmt() )); 212 this.getDeclareStatements().add(compoundSqlNode.getDeclareStmts().getStatementSqlNode(i).getStmt()); 213 } 214 } 215 216 if (compoundSqlNode.getStmts() != null){ 217 compoundSqlNode.getStmts().doParse(this,ESqlClause.unknown); 218 219 for(int i= 0; i<compoundSqlNode.getStmts().size();i++){ 220 this.getBodyStatements().add(compoundSqlNode.getStmts().getStatementSqlNode(i).getStmt()); 221 } 222 } 223 224 if (compoundSqlNode.getDeclareStmts() != null){ 225 // pop variable declare from symbolTable 226 for(int i=0;i<compoundSqlNode.getDeclareStmts().size();i++){ 227 this.getTopStatement().getSymbolTable().pop(); 228 } 229 } 230 } 231 break; 232 case dbvsnowflake: 233 if (createProcedureNode.getBlcok() != null){ 234 createProcedureNode.getBlcok().doParse(this,ESqlClause.unknown); 235 this.blockBody = createProcedureNode.getBlcok(); 236 }else{ 237 // javascript code body 238 } 239 240 break; 241 case dbvnetezza: 242 if (createProcedureNode.getStmts() != null){ 243 createProcedureNode.getStmts().doParse(this, ESqlClause.unknown); 244 245 TCommonBlock commonBlock = (TCommonBlock)createProcedureNode.getStmts().getStatementSqlNode(0).getStmt(); 246 this.setLabelName(commonBlock.getLabelName()); 247 this.blockBody = commonBlock.getBlockBody(); 248 this.blockBody.setParent(this); 249 250// for(int i=0;i<createProcedureNode.getStmts().size();i++){ 251// this.getBodyStatements().add(createProcedureNode.getStmts().getStatementSqlNode(i).getStmt()); 252// } 253 }else if ((createProcedureNode.getFunctionBody() != null)&&(getProcedureLanguage() != null)&&(getProcedureLanguage().toString() !=null)){ 254 // use constant in procedure body 255 processProcedureDefinition(EDbVendor.dbvnetezza,psql,createProcedureNode); 256 } 257 break; 258 default: 259 if (createProcedureNode.getDeclareStmts() != null){ 260 createProcedureNode.getDeclareStmts().doParse(this, ESqlClause.unknown); 261 262 // push variable declare into symbolTable , and add to declareStatements 263 for(int i=0;i<createProcedureNode.getDeclareStmts().size();i++){ 264 this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjVariable,this,createProcedureNode.getDeclareStmts().getStatementSqlNode(i).getStmt() )); 265 this.getDeclareStatements().add(createProcedureNode.getDeclareStmts().getStatementSqlNode(i).getStmt()); 266 } 267 268 } 269 270 if (createProcedureNode.getStmt() != null){ 271 createProcedureNode.getStmt().doParse(this, ESqlClause.unknown); 272 this.getBodyStatements().add(createProcedureNode.getStmt().getStmt()); 273 if (getRoutineBody() == null){ 274 setRoutineBody(createProcedureNode.getStmt().toString()); 275 } 276 } 277 else if (createProcedureNode.getBlcok() != null){ 278 createProcedureNode.getBlcok().doParse(this,ESqlClause.unknown); 279 this.blockBody = createProcedureNode.getBlcok(); 280 if (getRoutineBody() == null){ 281 setRoutineBody(createProcedureNode.getBlcok().toString()); 282 } 283 }else if (createProcedureNode.getStmts() != null){ //teradata,sql server 284 createProcedureNode.getStmts().doParse(this, ESqlClause.unknown); 285 286 // Teradata: validate semicolons between statements in procedure body 287 if (this.dbvendor == EDbVendor.dbvteradata) { 288 validateSemicolonsBetweenStatements(this, createProcedureNode.getStmts()); 289 } 290 291 for(int i=0;i<createProcedureNode.getStmts().size();i++){ 292 this.getBodyStatements().add(createProcedureNode.getStmts().getStatementSqlNode(i).getStmt()); 293 } 294 if (getRoutineBody() == null){ 295 String stmtsStr = createProcedureNode.getStmts().toString(); 296 if (stmtsStr == null && this.getBodyStatements().size() > 0){ 297 // Fallback: construct routineBody from body statements text 298 StringBuilder sb = new StringBuilder(); 299 for(int i=0;i<this.getBodyStatements().size();i++){ 300 if (i > 0) sb.append("\n"); 301 sb.append(this.getBodyStatements().get(i).toString()); 302 } 303 stmtsStr = sb.toString(); 304 } 305 if (stmtsStr != null){ 306 setRoutineBody(stmtsStr); 307 } 308 } 309 } 310 311 if (createProcedureNode.getExceptionClause() != null){ 312 createProcedureNode.getExceptionClause().doParse(this,ESqlClause.unknown); 313 this.setExceptionClause(createProcedureNode.getExceptionClause()); 314 } 315 316 if (createProcedureNode.getDeclareStmts() != null){ 317 318 // pop variable declare from symbolTable 319 for(int i=0;i<createProcedureNode.getDeclareStmts().size();i++){ 320 this.getTopStatement().getSymbolTable().pop(); 321 } 322 323 } 324 325 break; 326 327 } 328 329 // pop parameterDeclarations from symbolTable 330 if (this.getParameterDeclarations() != null){ 331 for(int i=0;i< this.getParameterDeclarations().size();i++){ 332 this.getTopStatement().getSymbolTable().pop(); 333 } 334 } 335 336 switch (this.dbvendor){ 337 case dbvpostgresql: 338 case dbvredshift: 339 if (createProcedureNode.getFunctionBody() != null){ 340 // function body only inside '' will be processed here 341 processProcedureDefinition(EDbVendor.dbvpostgresql,psql,createProcedureNode); 342 }else{ 343 // procedure body only inside $$ already processed in the above default section 344 } 345 346 break; 347 348 case dbvvertica: 349 processProcedureDefinition(EDbVendor.dbvpostgresql,psql,createProcedureNode); 350 break; 351 case dbvsnowflake: 352 processProcedureDefinition(this.dbvendor,psql,createProcedureNode); 353 break; 354 } 355 356 357 this.setEndlabelName(createProcedureNode.getEndlabelName()); 358 359 currentFrame.popMeFromStack(getFrameStack()); 360 361 return 0; 362 } 363 364 private void doParseFunctionBody(EDbVendor dbVendor,String inputSQL,boolean isSQLBlock,int bodyDeltaLine){ 365 TGSqlParser newParser = new TGSqlParser(dbVendor); 366 newParser.sqltext = inputSQL; 367 newParser.setSinglePLBlock(isSQLBlock); 368 newParser.setFrameStack(getFrameStack()); 369 int iRet = newParser.parse(); 370 TReparseCoordinateShifter.shift(newParser, bodyDeltaLine, 0); 371 if ((iRet == 0)&&(newParser.getSqlstatements().size() >0)){ 372 if (isSQLBlock){ 373 TCommonBlock commonBlock = (TCommonBlock)newParser.getSqlstatements().get(0); 374 this.setLabelName(commonBlock.getLabelName()); 375 this.blockBody = commonBlock.getBlockBody(); 376 this.blockBody.setParent(this); 377// if (this.blockBody.getDeclareStatements().size()>0){ 378// //this.getDeclareStatements().clear(); 379// for(int i=0;i<this.blockBody.getDeclareStatements().size();i++){ 380// this.getDeclareStatements().add(this.blockBody.getDeclareStatements().get(i)); 381// } 382// } 383 384// for(int i=0;i<commonBlock.getDeclareStatements().size();i++){ 385// this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjVariable,this,commonBlock.getDeclareStatements().get(i))); 386// this.getDeclareStatements().add(commonBlock.getDeclareStatements().get(i)); 387// } 388// for(int i=0;i<commonBlock.getBodyStatements().size();i++){ 389// commonBlock.getBodyStatements().get(i).setAlreadyAddToParent(false); 390// commonBlock.getBodyStatements().get(i).setParentStmt(this); 391// 392// this.getBodyStatements().add(commonBlock.getBodyStatements().get(i)); 393// } 394// if (commonBlock.getExceptionClause() != null){ 395// this.setExceptionClause(commonBlock.getExceptionClause()); 396// } 397// for(int i=0;i<commonBlock.getDeclareStatements().size();i++){ 398// this.getTopStatement().getSymbolTable().pop(); 399// } 400 }else{ 401 this.getBodyStatements().add(newParser.getSqlstatements().get(0)); 402 } 403 }else { 404 for(int j=0;j<newParser.getErrorCount();j++){ 405 this.parseerrormessagehandle(newParser.getSyntaxErrors().get(j)); 406 } 407 } 408 } 409 410 private void processProcedureDefinition(EDbVendor pDbVendor, TCustomSqlStatement psql, TCreateProcedureSqlNode createProcedureNode){ 411 boolean isSQLBlock = true; 412 String inputSQL = ""; 413 // Line offset applied to the re-parsed body after parsing, replacing the 414 // blank-line padding that stringBlock() capped at 1000. The column 415 // padding is kept (stringBlock first arg 0); only the line number needs 416 // shifting. A marker line ("plpgsql_function_delimiter\n") occupies 417 // parser line 1, so the body starts on parser line 2 -> delta (lineNo-2); 418 // without a marker the body starts on parser line 1 -> delta (lineNo-1). 419 int bodyDeltaLine = 0; 420 421 if ((createProcedureNode.getFunctionBody() != null)&&(getProcedureLanguage() == null || getProcedureLanguage().toString() !=null)){ 422 String langStr = (getProcedureLanguage() != null) ? getProcedureLanguage().toString() : "sql"; 423 if (langStr.equalsIgnoreCase("sql") 424 ||(langStr.equalsIgnoreCase("plpgsql")) 425 ||(langStr.equalsIgnoreCase("plvsql")) //vertica 426 ||(langStr.equalsIgnoreCase("nzplsql"))){ 427 428 String bodyStr = createProcedureNode.getFunctionBody().getStartToken().getQuotedString();//.trim(); 429 // CREATE OR REPLACE FUNCTION testspg__getString (varchar) RETURNS varchar as ' DECLARE inString alias for $1; begin return ''bob''; end; ' LANGUAGE plpgsql 430 // escaped quotes in string literals 431 // mantisbt/view.php?id=1331 432 if (createProcedureNode.getFunctionBody().getStartToken().toString().startsWith("'")){ 433 bodyStr = bodyStr.replaceAll("''","'"); 434 } 435 436 437 //System.out.println(bodyStr); 438 int testLen = 9; 439 // if (bodyStr.length() < testLen) testLen = bodyStr.length(); 440 String prefixStr = bodyStr.trim(); 441 if (prefixStr.length() < testLen) testLen = prefixStr.length(); 442 443 // String prefixStr = bodyStr.trim().substring(0,testLen).toLowerCase(); 444 prefixStr = prefixStr.substring(0,testLen).toLowerCase(); 445 446 if ((prefixStr.startsWith("declare"))||(prefixStr.startsWith("begin"))||(prefixStr.startsWith("<<"))){ 447 //bodyStr.replaceAll("''","'"); 448 //System.out.println(bodyStr); 449 450 // newParser.sqltext = "plpgsql_function_delimiter \n"+bodyStr; 451 bodyDeltaLine = (int)createProcedureNode.getFunctionBody().getStartToken().lineNo - 2; 452 inputSQL = "plpgsql_function_delimiter\n" 453 + TBaseType.stringBlock(0,(int)createProcedureNode.getFunctionBody().getStartToken().columnNo) 454 +bodyStr; 455 }else{ 456 //newParser.sqltext = bodyStr; 457 458 bodyDeltaLine = (int)createProcedureNode.getFunctionBody().getStartToken().lineNo - 1; 459 inputSQL = 460 TBaseType.stringBlock(0,(int)createProcedureNode.getFunctionBody().getStartToken().columnNo) 461 + bodyStr; 462 463 isSQLBlock = false; 464 } 465 466 467 } 468 }else if (createProcedureNode.getFunctionBodyTokenList() != null){ 469 //netezza, function body inside BEGIN_PROC...END_PROC 470 for(int i=1;i<createProcedureNode.getFunctionBodyTokenList().size()-1;i++){ 471 inputSQL = inputSQL+createProcedureNode.getFunctionBodyTokenList().get(i).toString(); 472 } 473 474 // Use a newline after the marker so the column padding lands the 475 // body at the right column on its own parser line (line 2); the line 476 // offset (lineNo - 2) is applied after parsing instead of via capped 477 // blank-line padding. 478 bodyDeltaLine = (int)createProcedureNode.getFunctionBodyTokenList().get(0).lineNo - 2; 479 inputSQL = "plpgsql_function_delimiter\n" 480 + TBaseType.stringBlock(0,(int)createProcedureNode.getFunctionBodyTokenList().get(0).columnNo) 481 +inputSQL; 482 isSQLBlock = true; 483 } 484 485 if (inputSQL.length() > 0){ 486 doParseFunctionBody(pDbVendor, inputSQL,isSQLBlock, bodyDeltaLine); 487 } 488 } 489 490 491 492 public void accept(TParseTreeVisitor v){ 493 v.preVisit(this); 494 v.postVisit(this); 495 } 496 497 public void acceptChildren(TParseTreeVisitor v){ 498 v.preVisit(this); 499 this.getProcedureName().acceptChildren(v); 500 if (getParameterDeclarations() != null) getParameterDeclarations().acceptChildren(v); 501 if (getBodyStatements().size() > 0) getBodyStatements().acceptChildren(v); 502 v.postVisit(this); 503 } 504 505 public void setProcedureName(TObjectName procedureName) { 506 this.procedureName = procedureName; 507 } 508 509 /** 510 * Validates that each statement in a Teradata procedure body (except the last) is terminated with a semicolon. 511 * Labels are exempt since they prefix the next statement without requiring a semicolon. 512 */ 513 public static void validateSemicolonsBetweenStatements(TCustomSqlStatement psql, TStatementListSqlNode stmtList) { 514 for (int i = 0; i < stmtList.size() - 1; i++) { 515 TStatementSqlNode stmtNode = stmtList.getStatementSqlNode(i); 516 TCustomSqlStatement innerStmt = stmtNode.getStmt(); 517 if (innerStmt == null) continue; 518 519 // Labels don't require semicolons (they prefix the next statement) 520 if (innerStmt.sqlstatementtype == ESqlStatementType.sstmssqllabel) { 521 continue; 522 } 523 524 // DECLARE statements (conditions, handlers, variables) don't require semicolons 525 // in Teradata procedure declaration sections 526 if (innerStmt.sqlstatementtype == ESqlStatementType.sstmssqldeclare 527 || innerStmt.sqlstatementtype == ESqlStatementType.sstplsql_vardecl) { 528 continue; 529 } 530 531 // Check for semicolon: first try the wrapper node's end token (set by grammar 532 // when semicolon is present), then fall back to the inner statement's end token 533 TSourceToken endToken = stmtNode.getEndToken(); 534 if (endToken == null) { 535 endToken = innerStmt.getEndToken(); 536 } 537 538 if (endToken == null || endToken.tokentype != ETokenType.ttsemicolon) { 539 // Missing semicolon - find the next statement's start token for error location 540 TStatementSqlNode nextStmtNode = stmtList.getStatementSqlNode(i + 1); 541 TCustomSqlStatement nextInnerStmt = nextStmtNode.getStmt(); 542 TSourceToken nextStart = nextStmtNode.getStartToken(); 543 if (nextStart == null && nextInnerStmt != null) { 544 nextStart = nextInnerStmt.getStartToken(); 545 } 546 if (nextStart != null) { 547 psql.getAncestorStmt().parseerrormessagehandle( 548 new TSyntaxError(nextStart, 549 "missing semicolon before", 550 EErrorType.spfatalerror, 551 10102, 552 psql.getAncestorStmt()) 553 ); 554 } 555 } 556 } 557 } 558}