001package gudusoft.gsqlparser.stmt; 002 003import gudusoft.gsqlparser.*; 004import gudusoft.gsqlparser.compiler.TFrame; 005import gudusoft.gsqlparser.compiler.TGlobalScope; 006import gudusoft.gsqlparser.compiler.TVariable; 007import gudusoft.gsqlparser.nodes.*; 008 009import java.util.ArrayList; 010import java.util.HashMap; 011import java.util.Stack; 012 013/** 014 * The EXECUTE IMMEDIATE statement builds and executes a dynamic SQL statement in 015 * a single operation. 016 */ 017public class TExecImmeStmt extends TBlockSqlStatement { 018 019 private TExpression dynamicStringExpr = null; 020 private TBindArgumentList bindArguments = null; 021 private TExpressionList intoVariables = null; 022 private TObjectNameList returnNames = null; 023 private String dynamicSQL = null; 024 private TStatementList dynamicStatements = null; 025 026 public ArrayList<String> getEvaluatedDynamicSQLs() { 027 return evaluatedDynamicSQLs; 028 } 029 030 private ArrayList <String> evaluatedDynamicSQLs = new ArrayList<String>(); 031 032 // Static parser instance for performance - reused across all dynamic SQL parsing 033 private static TGSqlParser sqlparser = null; 034 static { 035 sqlparser = new TGSqlParser(EDbVendor.dbvoracle); 036 } 037 038 /** 039 * 040 * @return sql statement instance that generated dynamically based on {@link #dynamicSQL} 041 */ 042 public synchronized TStatementList getDynamicStatements() { 043 if (this.dynamicStatements != null) return this.dynamicStatements; 044 if (this.getDynamicSQL() == null) return null; 045 046 // Prepare parser for reuse by clearing cached state 047 // This resets vendorParser, sqlEnv, sqlfilename, and parsing options 048 sqlparser.prepareForReuse(); 049 // Parse the dynamic SQL fragment as-is (no leading blank-line padding). 050 // The fragment's tokens are produced with coordinates relative to the 051 // fragment itself (first character at line 1). After parsing we shift 052 // every coordinate back to the position the fragment occupies in the 053 // original source file (see shiftDynamicCoordinates). 054 // 055 // Historically this method prepended (lineNo - 1) blank lines via 056 // TBaseType.stringBlock() so that the re-parsed tokens lined up with the 057 // file. That padding was capped at 1000 blank lines to avoid OOM, which 058 // produced wrong line numbers for any dynamic SQL located beyond line 059 // 1000 in the file (a fragment at file line 1709 was reported at 1005). 060 // Shifting coordinates after the fact is exact and allocates no padding. 061 sqlparser.sqltext = this.getDynamicSQL(); 062 // Propagate the enclosing routine's PL/SQL scope (parameters and local 063 // variables) into the dynamic-SQL parse, mirroring how the function / 064 // procedure body re-parse does it (TCreateFunctionStmt / 065 // TCreateProcedureStmt call newParser.setFrameStack(getFrameStack())). 066 // Without this, an identifier inside the dynamic SQL that is actually a 067 // routine parameter/variable (e.g. WHERE col = P_MANAGER_CODE) cannot be 068 // located by linkColumnToTable -> locateVariableOrParameter and is wrongly 069 // reported as an orphan column. With the scope available it resolves to 070 // the variable instead. 071 // 072 // This statement's own frame stack is usually empty here (a routine 073 // declared inside a package body does not push its parameter scope onto 074 // the frame stack), so when it is empty we rebuild one from the 075 // parameters / local variables of every enclosing routine. 076 // Always install a definite frame stack: sqlparser is a reused static 077 // parser and prepareForReuse() does not clear the frame stack, so a 078 // previous routine's scope must not leak into a later no-scope parse. 079 Stack<TFrame> dynFrameStack = getFrameStack(); 080 if (dynFrameStack == null || dynFrameStack.isEmpty()) { 081 Stack<TFrame> built = buildEnclosingRoutineFrameStack(); 082 dynFrameStack = (built != null) ? built : new Stack<TFrame>(); 083 } 084 sqlparser.setFrameStack(dynFrameStack); 085 int ret = sqlparser.parse(); 086 087 if(this.getDynamicStringExpr().getPlainTextLineNo() != -1){ 088 int deltaLine = (int) this.getDynamicStringExpr().getPlainTextLineNo() - 1; 089 int deltaColumn = (int) this.getDynamicStringExpr().getPlainTextColumnNo(); 090 TReparseCoordinateShifter.shift(sqlparser, deltaLine, deltaColumn); 091 } 092 093 if ( ret != 0){ 094 // a partially resolved value contains placeholder identifiers for 095 // unknowable parts (e.g. v_prefix || ' FROM t'); a parse failure 096 // on such text says nothing about the real SQL, so don't report it 097 if (!this.dynamicSQLPartial){ 098 for(int j=0;j<sqlparser.getErrorCount();j++){ 099 this.parseerrormessagehandle(sqlparser.getSyntaxErrors().get(j)); 100 } 101 } 102 103 return null; 104 } 105 106 this.dynamicStatements = new TStatementList(); 107 for(int i=0;i<sqlparser.sqlstatements.size();i++){ 108 if (this.getParentStmt() == null){ 109 sqlparser.sqlstatements.get(i).setParentStmt(this); 110 }else{ 111 sqlparser.sqlstatements.get(i).setParentStmt(this.getParentStmt()); 112 } 113 114 this.dynamicStatements.add(sqlparser.sqlstatements.get(i)); 115 } 116 return dynamicStatements; 117 } 118 119 /** 120 * Build a PL/SQL variable scope (frame stack) from the parameters and local 121 * variable declarations of every routine enclosing this EXECUTE IMMEDIATE, 122 * found by walking the parent-statement chain. 123 * <p> 124 * When a function / procedure is declared inside a package body its 125 * parameter scope is not pushed onto the frame stack, so this statement's 126 * own {@link #getFrameStack()} is empty and the dynamic-SQL parse would 127 * report routine parameters/variables referenced in the dynamic SQL (e.g. 128 * {@code WHERE col = P_MANAGER_CODE}) as orphan columns. Re-creating the 129 * scope here lets {@code locateVariableOrParameter} resolve those names to 130 * variables instead. Returns {@code null} when no enclosing routine 131 * contributes any name (e.g. a top-level EXECUTE IMMEDIATE). 132 */ 133 private Stack<TFrame> buildEnclosingRoutineFrameStack() { 134 TGlobalScope scope = null; 135 TCustomSqlStatement cur = this; 136 while (cur != null) { 137 if (cur instanceof TStoredProcedureSqlStatement) { 138 TStoredProcedureSqlStatement routine = (TStoredProcedureSqlStatement) cur; 139 140 TParameterDeclarationList params = routine.getParameterDeclarations(); 141 if (params != null) { 142 for (int i = 0; i < params.size(); i++) { 143 TParameterDeclaration pd = params.getParameterDeclarationItem(i); 144 if (pd != null && pd.getParameterName() != null) { 145 if (scope == null) scope = new TGlobalScope(); 146 scope.addSymbol(new TVariable(pd.getParameterName(), pd)); 147 } 148 } 149 } 150 151 TStatementList decls = routine.getDeclareStatements(); 152 if (decls != null) { 153 for (int i = 0; i < decls.size(); i++) { 154 TCustomSqlStatement d = decls.get(i); 155 if (d instanceof TVarDeclStmt && ((TVarDeclStmt) d).getElementName() != null) { 156 if (scope == null) scope = new TGlobalScope(); 157 scope.addSymbol(new TVariable(((TVarDeclStmt) d).getElementName(), d)); 158 } 159 } 160 } 161 } 162 cur = cur.getParentStmt(); 163 } 164 165 if (scope == null) return null; 166 Stack<TFrame> frameStack = new Stack<TFrame>(); 167 new TFrame(scope).pushMeToStack(frameStack); 168 return frameStack; 169 } 170 171 /** 172 * 173 * @return String representation of dynamic sql statement. if there is a variable in 174 * {@link #dynamicStringExpr}, value of this variable will be returned. 175 */ 176 177 public String getDynamicSQL() { 178 if (!this.getEvaluatedDynamicSQLs().isEmpty()){ 179 // 使用 TASTEvaluator 计算动态 SQL 后,将结果保存在 evaluatedDynamicSQLs 中,如果有值,采用这个更精确的 SQL 值 180 StringBuilder concatenatedSQL = new StringBuilder(); 181 182 for (int i = 0; i < this.getEvaluatedDynamicSQLs().size(); i++) { 183 String sql = this.getEvaluatedDynamicSQLs().get(i); 184 concatenatedSQL.append(sql); 185 if (i < this.getEvaluatedDynamicSQLs().size() - 1) { 186 concatenatedSQL.append(";"); 187 concatenatedSQL.append(TBaseType.newline); 188 } 189 } 190 191 this.dynamicSQL = concatenatedSQL.toString(); 192 } 193 return this.dynamicSQL; 194 195// if (this.dynamicSQL != null) return this.dynamicSQL; 196// 197// if (dynamicStringExpr.getExpressionType() == EExpressionType.simple_constant_t) 198// { 199// this.dynamicSQL = TBaseType.getStringInsideLiteral(this.dynamicStringExpr.toString()); 200// }else if (dynamicStringExpr.getExpressionType() == EExpressionType.simple_object_name_t){ 201// // this is a variable, we have to search 202// String lcvar = this.getDynamicStringExpr().toString(); 203// TCustomSqlStatement topsql = this.getTopStatement(); 204// if (topsql instanceof TBlockSqlStatement){ 205// TCustomSqlStatement stmt = null; 206// for(int i=0;i< ((TBlockSqlStatement)topsql).getBodyStatements().size();i++){ 207// stmt = ((TBlockSqlStatement)topsql).getBodyStatements().get(i); 208// if (stmt instanceof TAssignStmt){ 209// if ( ((TAssignStmt)stmt).getLeft().toString().compareToIgnoreCase(lcvar) == 0 ) 210// { 211// if (((TAssignStmt)stmt).getExpression().getExpressionType() == EExpressionType.simple_constant_t){ 212// this.dynamicSQL = ((TAssignStmt)stmt).getExpression().toString().substring(1,((TAssignStmt)stmt).getExpression().toString().length() - 1); 213// }else{ 214// this.dynamicSQL = null; 215// } 216// break; 217// } 218// } 219// } 220// } 221// if ((this.getParentStmt() instanceof TBlockSqlStatement)&&(this.dynamicSQL == null)){ 222// TBlockSqlStatement blockSqlStatement = (TBlockSqlStatement)this.getParentStmt(); 223// for(int i=0;i< blockSqlStatement.getBodyStatements().size();i++){ 224// TCustomSqlStatement stmt = blockSqlStatement.getBodyStatements().get(i); 225// if (stmt instanceof TAssignStmt){ 226// if ( ((TAssignStmt)stmt).getLeft().toString().compareToIgnoreCase(lcvar) == 0 ) 227// { 228// if (((TAssignStmt)stmt).getExpression().getExpressionType() == EExpressionType.simple_constant_t){ 229// this.dynamicSQL = ((TAssignStmt)stmt).getExpression().toString().substring(1,((TAssignStmt)stmt).getExpression().toString().length() - 1); 230// }else{ 231// this.dynamicSQL = null; 232// } 233// break; 234// } 235// } 236// } 237// } 238// 239// }else{ 240// calculateExprVisitor cv = new calculateExprVisitor(); 241// this.dynamicStringExpr.postOrderTraverse(cv); 242// //this.dynamicStringExpr.evaluate(); 243// 244// this.dynamicSQL = this.dynamicStringExpr.getPlainText(); 245//// if (this.dynamicSQL.charAt(0) == '\''){ 246//// this.dynamicSQL = this.dynamicSQL.substring(1,this.dynamicSQL.toString().length() - 2); 247//// } 248// } 249// return this.dynamicSQL ; 250 } 251 252 /** 253 * bind arguments 254 * @return bind arguments in using clause. 255 */ 256 public TBindArgumentList getBindArguments() { 257 return bindArguments; 258 } 259 260 /** 261 * String expr 262 * 263 * @return A string literal, string variable, or string expression that represents any SQL statement. 264 * this is the original string of dynamic sql statement. 265 */ 266 267 public TExpression getDynamicStringExpr() { 268 return dynamicStringExpr; 269 } 270 271 /** 272 * Into variable 273 * 274 * @return variable names in the into clause. 275 */ 276 public TExpressionList getIntoVariables() { 277 return intoVariables; 278 } 279 280 /** 281 * 282 * Used if and only if dynamic_sql_stmt has a RETURNING INTO clause, this clause 283 * returns the column values of the rows affected by dynamic_sql_stmt, in either 284 * individual variables or records 285 * 286 * @return 287 */ 288 289 public TObjectNameList getReturnNames() { 290 return returnNames; 291 } 292 293 public TExecImmeStmt(EDbVendor dbvendor){ 294 super(dbvendor); 295 sqlstatementtype = ESqlStatementType.sstplsql_execimmestmt ; 296 } 297 298 void buildsql() { 299 } 300 301 void clear() { 302 } 303 304 String getasprettytext() { 305 return ""; 306 } 307 308 void iterate(TVisitorAbs pvisitor) { 309 } 310 311 public int doParseStatement(TCustomSqlStatement psql) { 312 if (rootNode == null) return -1; 313 super.doParseStatement(psql); 314 315 TExecImmeNode execImmeNode = (TExecImmeNode)rootNode; 316 this.bindArguments = execImmeNode.getBindArguments(); 317 this.intoVariables = execImmeNode.getIntoVariables(); 318 this.dynamicStringExpr = execImmeNode.getDynamicStringExpr(); 319 this.returnNames = execImmeNode.getReturnNames(); 320 321 this.dynamicStringExpr.evaluate(this.getFrameStack(),this); 322 // only treat the evaluated text as dynamic SQL when the expression 323 // resolved to an actual string value. When the value is unknowable 324 // (unassigned variable, value from a table column, bare function call) 325 // the plain text is just a placeholder identifier; parsing it as SQL 326 // produces a bogus "Error when tokenize" report (MantisBT 4503) 327 if (this.dynamicStringExpr.getVal() != null){ 328 this.dynamicSQL = this.dynamicStringExpr.getPlainText(); 329 this.dynamicSQLPartial = this.dynamicStringExpr.isValPartial(); 330 } 331 332 //TODO parse the dynamicSQL, 先不在这里解析动态 SQL, 考虑和 TASTEvaluator 结合 333 // getDynamicStatements(); 334 335 return 0; 336 } 337 338 public void accept(TParseTreeVisitor v){ 339 v.preVisit(this); 340 v.postVisit(this); 341 } 342 343 public void acceptChildren(TParseTreeVisitor v){ 344 v.preVisit(this); 345 this.dynamicStringExpr.acceptChildren(v); 346 v.postVisit(this); 347 } 348 349 public void setDynamicStringExpr(TExpression dynamicStringExpr) { 350 this.dynamicStringExpr = dynamicStringExpr; 351 } 352 353 public void setBindArguments(TBindArgumentList bindArguments) { 354 this.bindArguments = bindArguments; 355 } 356 357 public void setIntoVariables(TExpressionList intoVariables) { 358 this.intoVariables = intoVariables; 359 } 360 361 public void setReturnNames(TObjectNameList returnNames) { 362 this.returnNames = returnNames; 363 } 364 365 public void setDynamicSQL(String dynamicSQL) { 366 this.dynamicSQL = dynamicSQL; 367 } 368 369 private boolean dynamicSQLPartial = false; 370 371 /** 372 * True when {@link #getDynamicSQL()} was only partially resolved: it 373 * contains placeholder identifiers for unknowable parts (unresolved 374 * variables, function calls) alongside resolved literal text. Such text 375 * is still worth analyzing for approximate lineage, but a parse failure 376 * on it must not be reported as a syntax error. 377 */ 378 public boolean isDynamicSQLPartial() { 379 return dynamicSQLPartial; 380 } 381 382 public void setDynamicStatements(TStatementList dynamicStatements) { 383 this.dynamicStatements = dynamicStatements; 384 } 385 386// private String EvaluateExpr(){ 387// if (this.dynamicStringExpr == null) return ""; 388// return this.dynamicStringExpr.getPlainText(); 389// } 390} 391 392//class calculateExprVisitor implements IExpressionVisitor { 393// Stack<TExpression> expressionStack = new Stack<>(); 394// 395// public boolean exprVisit(TParseTreeNode pNode,boolean isLeafNode){ 396// if (isLeafNode){ 397// expressionStack.push((TExpression)pNode); 398// } 399// 400// TExpression expr = (TExpression)pNode; 401// switch (expr.getExpressionType()){ 402// case concatenate_t: 403// TExpression expr1 = expressionStack.pop(); 404// TExpression expr2 = expressionStack.pop(); 405// 406// String expr1Str = expr1.getPlainText(); 407// String expr2Str = expr2.getPlainText(); 408// switch (expr1.getExpressionType()){ 409// case simple_constant_t: 410// expr1Str = TBaseType.getStringInsideLiteral(expr1Str); 411// break; 412// case function_t: 413// expr1Str = expr1.getFunctionCall().getFunctionName().toString(); 414// break; 415// case simple_object_name_t: 416// expr1Str = expr1Str.replace(".","_"); 417// break; 418// default: 419// break; 420// } 421// 422// switch (expr2.getExpressionType()){ 423// case simple_constant_t: 424// expr2Str = TBaseType.getStringInsideLiteral(expr2Str); 425// break; 426// case function_t: 427// expr2Str = expr2.getFunctionCall().getFunctionName().toString(); 428// break; 429// case simple_object_name_t: 430// expr2Str = expr2Str.replace(".","_"); 431// break; 432// default: 433// break; 434// } 435// 436// 437// //TExpression expr3 = expressionStack.peek(); 438// ((TExpression)pNode).setPlainText(expr2Str+expr1Str); 439// 440// expressionStack.push((TExpression)pNode); 441// 442// break; 443// } 444// return true; 445// }; 446// 447//} 448