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 041 public TConstant getProcedureLanguage() { 042 return procedureLanguage; 043 } 044 045 public TConstant getLinkSymbol() { 046 return linkSymbol; 047 } 048 public TConstant getObjfile() { 049 return objfile; 050 } 051 052 private TPTNodeList <TProcedureOption> procedureOptions; 053 private boolean isForReplication; 054 055 public void setProcedureOptions(TPTNodeList<TProcedureOption> procedureOptions) { 056 this.procedureOptions = procedureOptions; 057 } 058 059 public TPTNodeList<TProcedureOption> getProcedureOptions() { 060 061 return procedureOptions; 062 } 063 064 public void setForReplication(boolean isForReplication) { 065 this.isForReplication = isForReplication; 066 } 067 068 public boolean isForReplication() { 069 return isForReplication; 070 } 071 072 public TCreateProcedureStmt (EDbVendor dbvendor){ 073 super(dbvendor); 074 sqlstatementtype = ESqlStatementType.sstcreateprocedure ; 075 } 076 077 @Override 078 public TObjectName getStoredProcedureName(){ 079 return procedureName; 080 } 081 082 /** 083 * The name that you give to the procedure that you are declaring or defining. 084 * @return 085 */ 086 public TObjectName getProcedureName() { 087 return procedureName; 088 } 089 090 private TObjectName procedureName = null; 091 092 093 094 public int doParseStatement(TCustomSqlStatement psql) { 095 if (rootNode == null) return -1; 096 TCreateProcedureSqlNode createProcedureNode = (TCreateProcedureSqlNode)rootNode; 097 098 super.doParseStatement(psql); 099 TFrame currentFrame = new TFrame(this.stmtScope); 100 currentFrame.pushMeToStack(getFrameStack()); 101 102 procedureName = createProcedureNode.getProcedureName(); 103 104 if (getSqlEnv() != null) { 105 getSqlEnv().addProcedure(procedureName.toString(),true); 106 107 // move to TDatabaseObjectResolver 108 109// if (getSqlEnv().getDefaultCatalogName() != null){ 110// if (procedureName.getDatabaseToken() == null){ 111// procedureName.setDatabaseToken(new TSourceToken(getSqlEnv().getDefaultCatalogName())); 112// } 113// } 114 } 115 116 setRoutineName(procedureName); 117 118 procedureLanguage = createProcedureNode.getProcedureLanguage(); 119 if (procedureLanguage != null){ 120 if (getRoutineLanguage() == null){ 121 // not already set during TGsqlParser.dopostgresqlgetrawsqlstatements() 122 // then we set it here 123 setRoutineLanguage(procedureLanguage.toString()); 124 } 125 setRoutineLanguageInConstant(procedureLanguage); 126 }else { 127 128 if (dbvendor == EDbVendor.dbvvertica){ 129 // vertica, set default to plvsql 130 TConstant c = new TConstant(); 131 c.init(ELiteralType.etString, new TSourceToken("plvsql")); 132 procedureLanguage = c; 133 setRoutineLanguageInConstant(procedureLanguage); 134 setRoutineLanguage("plvsql"); 135 } 136 137 } 138 this.setLabelName(createProcedureNode.getLabelName()); 139 objfile = createProcedureNode.getObjfile(); 140 linkSymbol = createProcedureNode.getLinkSymbol(); 141 this.setReturnDataType(createProcedureNode.getReturnDataType()); 142 if (createProcedureNode.getFunctionBody() != null){ 143 setRoutineBodyInConstant(createProcedureNode.getFunctionBody()); 144 setRoutineBody(createProcedureNode.getFunctionBody().toString()); 145 } 146 147 this.setParameterDeclarations( createProcedureNode.getParameters()); 148 // sql server 149 procedureOptions = createProcedureNode.getProcedureOptions(); 150 isForReplication = createProcedureNode.isForReplication(); 151 this.setReturnDataType(createProcedureNode.getReturnDataType()); 152 // end sql server 153 154 // push parameterDeclarations into symbolTable 155 if (this.getParameterDeclarations() != null){ 156 for(int i=0;i< this.getParameterDeclarations().size();i++){ 157 this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjParameter,this, this.getParameterDeclarations().getParameterDeclarationItem(i))); 158 TParameterDeclaration parameterDeclaration = this.getParameterDeclarations().getParameterDeclarationItem(i); 159 if (parameterDeclaration.getParameterName() != null){ 160 TVariable variable = new TVariable(parameterDeclaration.getParameterName(),parameterDeclaration,procedureName); 161 162 TVariable variableOfThisParam = TSymbolTableManager.searchSymbolVariable(this.getFrameStack(),procedureName+".$"+i); 163 if (variableOfThisParam != null){ 164 variable.setVariableStr(variableOfThisParam.getVariableStr()); 165 } 166 167 // variable.variableStr = TBaseType.getStringInsideLiteral(getSqlEnv().getProcedureParameterValue(procedureName.toString(), i)); 168 this.stmtScope.addSymbol(variable); 169 } 170 } 171 } 172 173 174 switch (dbvendor){ 175 case dbvdb2: 176 TCompoundSqlNode compoundSqlNode = createProcedureNode.getCompoundSqls(); 177 if (compoundSqlNode != null){ 178 if (compoundSqlNode.getDeclareStmts() != null){ 179 compoundSqlNode.getDeclareStmts().doParse(this,ESqlClause.unknown); 180 181 // push variable declare into symbolTable, and add to declareStatements 182 for(int i=0;i<compoundSqlNode.getDeclareStmts().size();i++){ 183 this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjVariable,this,compoundSqlNode.getDeclareStmts().getStatementSqlNode(i).getStmt() )); 184 this.getDeclareStatements().add(compoundSqlNode.getDeclareStmts().getStatementSqlNode(i).getStmt()); 185 } 186 } 187 188 if (compoundSqlNode.getStmts() != null){ 189 compoundSqlNode.getStmts().doParse(this,ESqlClause.unknown); 190 191 for(int i= 0; i<compoundSqlNode.getStmts().size();i++){ 192 this.getBodyStatements().add(compoundSqlNode.getStmts().getStatementSqlNode(i).getStmt()); 193 } 194 } 195 196 if (compoundSqlNode.getDeclareStmts() != null){ 197 // pop variable declare from symbolTable 198 for(int i=0;i<compoundSqlNode.getDeclareStmts().size();i++){ 199 this.getTopStatement().getSymbolTable().pop(); 200 } 201 } 202 } 203 break; 204 case dbvsnowflake: 205 if (createProcedureNode.getBlcok() != null){ 206 createProcedureNode.getBlcok().doParse(this,ESqlClause.unknown); 207 this.blockBody = createProcedureNode.getBlcok(); 208 }else{ 209 // javascript code body 210 } 211 212 break; 213 case dbvnetezza: 214 if (createProcedureNode.getStmts() != null){ 215 createProcedureNode.getStmts().doParse(this, ESqlClause.unknown); 216 217 TCommonBlock commonBlock = (TCommonBlock)createProcedureNode.getStmts().getStatementSqlNode(0).getStmt(); 218 this.setLabelName(commonBlock.getLabelName()); 219 this.blockBody = commonBlock.getBlockBody(); 220 this.blockBody.setParent(this); 221 222// for(int i=0;i<createProcedureNode.getStmts().size();i++){ 223// this.getBodyStatements().add(createProcedureNode.getStmts().getStatementSqlNode(i).getStmt()); 224// } 225 }else if ((createProcedureNode.getFunctionBody() != null)&&(getProcedureLanguage().toString() !=null)){ 226 // use constant in procedure body 227 processProcedureDefinition(EDbVendor.dbvnetezza,psql,createProcedureNode); 228 } 229 break; 230 default: 231 if (createProcedureNode.getDeclareStmts() != null){ 232 createProcedureNode.getDeclareStmts().doParse(this, ESqlClause.unknown); 233 234 // push variable declare into symbolTable , and add to declareStatements 235 for(int i=0;i<createProcedureNode.getDeclareStmts().size();i++){ 236 this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjVariable,this,createProcedureNode.getDeclareStmts().getStatementSqlNode(i).getStmt() )); 237 this.getDeclareStatements().add(createProcedureNode.getDeclareStmts().getStatementSqlNode(i).getStmt()); 238 } 239 240 } 241 242 if (createProcedureNode.getStmt() != null){ 243 createProcedureNode.getStmt().doParse(this, ESqlClause.unknown); 244 this.getBodyStatements().add(createProcedureNode.getStmt().getStmt()); 245 } 246 else if (createProcedureNode.getBlcok() != null){ 247 createProcedureNode.getBlcok().doParse(this,ESqlClause.unknown); 248 this.blockBody = createProcedureNode.getBlcok(); 249 }else if (createProcedureNode.getStmts() != null){ //teradata,sql server 250 createProcedureNode.getStmts().doParse(this, ESqlClause.unknown); 251 for(int i=0;i<createProcedureNode.getStmts().size();i++){ 252 this.getBodyStatements().add(createProcedureNode.getStmts().getStatementSqlNode(i).getStmt()); 253 } 254 } 255 256 if (createProcedureNode.getExceptionClause() != null){ 257 createProcedureNode.getExceptionClause().doParse(this,ESqlClause.unknown); 258 this.setExceptionClause(createProcedureNode.getExceptionClause()); 259 } 260 261 if (createProcedureNode.getDeclareStmts() != null){ 262 263 // pop variable declare from symbolTable 264 for(int i=0;i<createProcedureNode.getDeclareStmts().size();i++){ 265 this.getTopStatement().getSymbolTable().pop(); 266 } 267 268 } 269 270 break; 271 272 } 273 274 // pop parameterDeclarations from symbolTable 275 if (this.getParameterDeclarations() != null){ 276 for(int i=0;i< this.getParameterDeclarations().size();i++){ 277 this.getTopStatement().getSymbolTable().pop(); 278 } 279 } 280 281 switch (this.dbvendor){ 282 case dbvpostgresql: 283 case dbvredshift: 284 if (createProcedureNode.getFunctionBody() != null){ 285 // function body only inside '' will be processed here 286 processProcedureDefinition(EDbVendor.dbvpostgresql,psql,createProcedureNode); 287 }else{ 288 // procedure body only inside $$ already processed in the above default section 289 } 290 291 break; 292 293 case dbvvertica: 294 processProcedureDefinition(EDbVendor.dbvpostgresql,psql,createProcedureNode); 295 break; 296 case dbvsnowflake: 297 processProcedureDefinition(this.dbvendor,psql,createProcedureNode); 298 break; 299 } 300 301 302 this.setEndlabelName(createProcedureNode.getEndlabelName()); 303 304 currentFrame.popMeFromStack(getFrameStack()); 305 306 return 0; 307 } 308 309 private void doParseFunctionBody(EDbVendor dbVendor,String inputSQL,boolean isSQLBlock){ 310 TGSqlParser newParser = new TGSqlParser(dbVendor); 311 newParser.sqltext = inputSQL; 312 newParser.setSinglePLBlock(isSQLBlock); 313 newParser.setFrameStack(getFrameStack()); 314 int iRet = newParser.parse(); 315 if ((iRet == 0)&&(newParser.getSqlstatements().size() >0)){ 316 if (isSQLBlock){ 317 TCommonBlock commonBlock = (TCommonBlock)newParser.getSqlstatements().get(0); 318 this.setLabelName(commonBlock.getLabelName()); 319 this.blockBody = commonBlock.getBlockBody(); 320 this.blockBody.setParent(this); 321// if (this.blockBody.getDeclareStatements().size()>0){ 322// //this.getDeclareStatements().clear(); 323// for(int i=0;i<this.blockBody.getDeclareStatements().size();i++){ 324// this.getDeclareStatements().add(this.blockBody.getDeclareStatements().get(i)); 325// } 326// } 327 328// for(int i=0;i<commonBlock.getDeclareStatements().size();i++){ 329// this.getTopStatement().getSymbolTable().push( new TSymbolTableItem(TObjectName.ttobjVariable,this,commonBlock.getDeclareStatements().get(i))); 330// this.getDeclareStatements().add(commonBlock.getDeclareStatements().get(i)); 331// } 332// for(int i=0;i<commonBlock.getBodyStatements().size();i++){ 333// commonBlock.getBodyStatements().get(i).setAlreadyAddToParent(false); 334// commonBlock.getBodyStatements().get(i).setParentStmt(this); 335// 336// this.getBodyStatements().add(commonBlock.getBodyStatements().get(i)); 337// } 338// if (commonBlock.getExceptionClause() != null){ 339// this.setExceptionClause(commonBlock.getExceptionClause()); 340// } 341// for(int i=0;i<commonBlock.getDeclareStatements().size();i++){ 342// this.getTopStatement().getSymbolTable().pop(); 343// } 344 }else{ 345 this.getBodyStatements().add(newParser.getSqlstatements().get(0)); 346 } 347 }else { 348 for(int j=0;j<newParser.getErrorCount();j++){ 349 this.parseerrormessagehandle(newParser.getSyntaxErrors().get(j)); 350 } 351 } 352 } 353 354 private void processProcedureDefinition(EDbVendor pDbVendor, TCustomSqlStatement psql, TCreateProcedureSqlNode createProcedureNode){ 355 boolean isSQLBlock = true; 356 String inputSQL = ""; 357 358 if ((createProcedureNode.getFunctionBody() != null)&&(getProcedureLanguage().toString() !=null)){ 359 if (getProcedureLanguage().toString().equalsIgnoreCase("sql") 360 ||(getProcedureLanguage().toString().equalsIgnoreCase("plpgsql")) 361 ||(getProcedureLanguage().toString().equalsIgnoreCase("plvsql")) //vertica 362 ||(getProcedureLanguage().toString().equalsIgnoreCase("nzplsql"))){ 363 364 String bodyStr = createProcedureNode.getFunctionBody().getStartToken().getQuotedString();//.trim(); 365 // CREATE OR REPLACE FUNCTION testspg__getString (varchar) RETURNS varchar as ' DECLARE inString alias for $1; begin return ''bob''; end; ' LANGUAGE plpgsql 366 // escaped quotes in string literals 367 // mantisbt/view.php?id=1331 368 if (createProcedureNode.getFunctionBody().getStartToken().toString().startsWith("'")){ 369 bodyStr = bodyStr.replaceAll("''","'"); 370 } 371 372 373 //System.out.println(bodyStr); 374 int testLen = 9; 375 // if (bodyStr.length() < testLen) testLen = bodyStr.length(); 376 String prefixStr = bodyStr.trim(); 377 if (prefixStr.length() < testLen) testLen = prefixStr.length(); 378 379 // String prefixStr = bodyStr.trim().substring(0,testLen).toLowerCase(); 380 prefixStr = prefixStr.substring(0,testLen).toLowerCase(); 381 382 if ((prefixStr.startsWith("declare"))||(prefixStr.startsWith("begin"))||(prefixStr.startsWith("<<"))){ 383 //bodyStr.replaceAll("''","'"); 384 //System.out.println(bodyStr); 385 386 // newParser.sqltext = "plpgsql_function_delimiter \n"+bodyStr; 387 inputSQL = "plpgsql_function_delimiter\n" 388 + TBaseType.stringBlock((int)createProcedureNode.getFunctionBody().getStartToken().lineNo - 2,(int)createProcedureNode.getFunctionBody().getStartToken().columnNo) 389 +bodyStr; 390 }else{ 391 //newParser.sqltext = bodyStr; 392 393 inputSQL = 394 TBaseType.stringBlock((int)createProcedureNode.getFunctionBody().getStartToken().lineNo - 1,(int)createProcedureNode.getFunctionBody().getStartToken().columnNo) 395 + bodyStr; 396 397 isSQLBlock = false; 398 } 399 400 401 } 402 }else if (createProcedureNode.getFunctionBodyTokenList() != null){ 403 //netezza, function body inside BEGIN_PROC...END_PROC 404 for(int i=1;i<createProcedureNode.getFunctionBodyTokenList().size()-1;i++){ 405 inputSQL = inputSQL+createProcedureNode.getFunctionBodyTokenList().get(i).toString(); 406 } 407 408 inputSQL = "plpgsql_function_delimiter " 409 + TBaseType.stringBlock((int)createProcedureNode.getFunctionBodyTokenList().get(0).lineNo - 1,(int)createProcedureNode.getFunctionBodyTokenList().get(0).columnNo) 410 +inputSQL; 411 isSQLBlock = true; 412 } 413 414 if (inputSQL.length() > 0){ 415 // System.out.println(inputSQL); 416 doParseFunctionBody(pDbVendor, inputSQL,isSQLBlock); 417 } 418 } 419 420 421 422 public void accept(TParseTreeVisitor v){ 423 v.preVisit(this); 424 v.postVisit(this); 425 } 426 427 public void acceptChildren(TParseTreeVisitor v){ 428 v.preVisit(this); 429 this.getProcedureName().acceptChildren(v); 430 if (getParameterDeclarations() != null) getParameterDeclarations().acceptChildren(v); 431 if (getBodyStatements().size() > 0) getBodyStatements().acceptChildren(v); 432 v.postVisit(this); 433 } 434 435 public void setProcedureName(TObjectName procedureName) { 436 this.procedureName = procedureName; 437 } 438}