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 for(int j=0;j<sqlparser.getErrorCount();j++){ 095 this.parseerrormessagehandle(sqlparser.getSyntaxErrors().get(j)); 096 } 097 098 return null; 099 } 100 101 this.dynamicStatements = new TStatementList(); 102 for(int i=0;i<sqlparser.sqlstatements.size();i++){ 103 if (this.getParentStmt() == null){ 104 sqlparser.sqlstatements.get(i).setParentStmt(this); 105 }else{ 106 sqlparser.sqlstatements.get(i).setParentStmt(this.getParentStmt()); 107 } 108 109 this.dynamicStatements.add(sqlparser.sqlstatements.get(i)); 110 } 111 return dynamicStatements; 112 } 113 114 /** 115 * Build a PL/SQL variable scope (frame stack) from the parameters and local 116 * variable declarations of every routine enclosing this EXECUTE IMMEDIATE, 117 * found by walking the parent-statement chain. 118 * <p> 119 * When a function / procedure is declared inside a package body its 120 * parameter scope is not pushed onto the frame stack, so this statement's 121 * own {@link #getFrameStack()} is empty and the dynamic-SQL parse would 122 * report routine parameters/variables referenced in the dynamic SQL (e.g. 123 * {@code WHERE col = P_MANAGER_CODE}) as orphan columns. Re-creating the 124 * scope here lets {@code locateVariableOrParameter} resolve those names to 125 * variables instead. Returns {@code null} when no enclosing routine 126 * contributes any name (e.g. a top-level EXECUTE IMMEDIATE). 127 */ 128 private Stack<TFrame> buildEnclosingRoutineFrameStack() { 129 TGlobalScope scope = null; 130 TCustomSqlStatement cur = this; 131 while (cur != null) { 132 if (cur instanceof TStoredProcedureSqlStatement) { 133 TStoredProcedureSqlStatement routine = (TStoredProcedureSqlStatement) cur; 134 135 TParameterDeclarationList params = routine.getParameterDeclarations(); 136 if (params != null) { 137 for (int i = 0; i < params.size(); i++) { 138 TParameterDeclaration pd = params.getParameterDeclarationItem(i); 139 if (pd != null && pd.getParameterName() != null) { 140 if (scope == null) scope = new TGlobalScope(); 141 scope.addSymbol(new TVariable(pd.getParameterName(), pd)); 142 } 143 } 144 } 145 146 TStatementList decls = routine.getDeclareStatements(); 147 if (decls != null) { 148 for (int i = 0; i < decls.size(); i++) { 149 TCustomSqlStatement d = decls.get(i); 150 if (d instanceof TVarDeclStmt && ((TVarDeclStmt) d).getElementName() != null) { 151 if (scope == null) scope = new TGlobalScope(); 152 scope.addSymbol(new TVariable(((TVarDeclStmt) d).getElementName(), d)); 153 } 154 } 155 } 156 } 157 cur = cur.getParentStmt(); 158 } 159 160 if (scope == null) return null; 161 Stack<TFrame> frameStack = new Stack<TFrame>(); 162 new TFrame(scope).pushMeToStack(frameStack); 163 return frameStack; 164 } 165 166 /** 167 * 168 * @return String representation of dynamic sql statement. if there is a variable in 169 * {@link #dynamicStringExpr}, value of this variable will be returned. 170 */ 171 172 public String getDynamicSQL() { 173 if (!this.getEvaluatedDynamicSQLs().isEmpty()){ 174 // 使用 TASTEvaluator 计算动态 SQL 后,将结果保存在 evaluatedDynamicSQLs 中,如果有值,采用这个更精确的 SQL 值 175 StringBuilder concatenatedSQL = new StringBuilder(); 176 177 for (int i = 0; i < this.getEvaluatedDynamicSQLs().size(); i++) { 178 String sql = this.getEvaluatedDynamicSQLs().get(i); 179 concatenatedSQL.append(sql); 180 if (i < this.getEvaluatedDynamicSQLs().size() - 1) { 181 concatenatedSQL.append(";"); 182 concatenatedSQL.append(TBaseType.newline); 183 } 184 } 185 186 this.dynamicSQL = concatenatedSQL.toString(); 187 } 188 return this.dynamicSQL; 189 190// if (this.dynamicSQL != null) return this.dynamicSQL; 191// 192// if (dynamicStringExpr.getExpressionType() == EExpressionType.simple_constant_t) 193// { 194// this.dynamicSQL = TBaseType.getStringInsideLiteral(this.dynamicStringExpr.toString()); 195// }else if (dynamicStringExpr.getExpressionType() == EExpressionType.simple_object_name_t){ 196// // this is a variable, we have to search 197// String lcvar = this.getDynamicStringExpr().toString(); 198// TCustomSqlStatement topsql = this.getTopStatement(); 199// if (topsql instanceof TBlockSqlStatement){ 200// TCustomSqlStatement stmt = null; 201// for(int i=0;i< ((TBlockSqlStatement)topsql).getBodyStatements().size();i++){ 202// stmt = ((TBlockSqlStatement)topsql).getBodyStatements().get(i); 203// if (stmt instanceof TAssignStmt){ 204// if ( ((TAssignStmt)stmt).getLeft().toString().compareToIgnoreCase(lcvar) == 0 ) 205// { 206// if (((TAssignStmt)stmt).getExpression().getExpressionType() == EExpressionType.simple_constant_t){ 207// this.dynamicSQL = ((TAssignStmt)stmt).getExpression().toString().substring(1,((TAssignStmt)stmt).getExpression().toString().length() - 1); 208// }else{ 209// this.dynamicSQL = null; 210// } 211// break; 212// } 213// } 214// } 215// } 216// if ((this.getParentStmt() instanceof TBlockSqlStatement)&&(this.dynamicSQL == null)){ 217// TBlockSqlStatement blockSqlStatement = (TBlockSqlStatement)this.getParentStmt(); 218// for(int i=0;i< blockSqlStatement.getBodyStatements().size();i++){ 219// TCustomSqlStatement stmt = blockSqlStatement.getBodyStatements().get(i); 220// if (stmt instanceof TAssignStmt){ 221// if ( ((TAssignStmt)stmt).getLeft().toString().compareToIgnoreCase(lcvar) == 0 ) 222// { 223// if (((TAssignStmt)stmt).getExpression().getExpressionType() == EExpressionType.simple_constant_t){ 224// this.dynamicSQL = ((TAssignStmt)stmt).getExpression().toString().substring(1,((TAssignStmt)stmt).getExpression().toString().length() - 1); 225// }else{ 226// this.dynamicSQL = null; 227// } 228// break; 229// } 230// } 231// } 232// } 233// 234// }else{ 235// calculateExprVisitor cv = new calculateExprVisitor(); 236// this.dynamicStringExpr.postOrderTraverse(cv); 237// //this.dynamicStringExpr.evaluate(); 238// 239// this.dynamicSQL = this.dynamicStringExpr.getPlainText(); 240//// if (this.dynamicSQL.charAt(0) == '\''){ 241//// this.dynamicSQL = this.dynamicSQL.substring(1,this.dynamicSQL.toString().length() - 2); 242//// } 243// } 244// return this.dynamicSQL ; 245 } 246 247 /** 248 * bind arguments 249 * @return bind arguments in using clause. 250 */ 251 public TBindArgumentList getBindArguments() { 252 return bindArguments; 253 } 254 255 /** 256 * String expr 257 * 258 * @return A string literal, string variable, or string expression that represents any SQL statement. 259 * this is the original string of dynamic sql statement. 260 */ 261 262 public TExpression getDynamicStringExpr() { 263 return dynamicStringExpr; 264 } 265 266 /** 267 * Into variable 268 * 269 * @return variable names in the into clause. 270 */ 271 public TExpressionList getIntoVariables() { 272 return intoVariables; 273 } 274 275 /** 276 * 277 * Used if and only if dynamic_sql_stmt has a RETURNING INTO clause, this clause 278 * returns the column values of the rows affected by dynamic_sql_stmt, in either 279 * individual variables or records 280 * 281 * @return 282 */ 283 284 public TObjectNameList getReturnNames() { 285 return returnNames; 286 } 287 288 public TExecImmeStmt(EDbVendor dbvendor){ 289 super(dbvendor); 290 sqlstatementtype = ESqlStatementType.sstplsql_execimmestmt ; 291 } 292 293 void buildsql() { 294 } 295 296 void clear() { 297 } 298 299 String getasprettytext() { 300 return ""; 301 } 302 303 void iterate(TVisitorAbs pvisitor) { 304 } 305 306 public int doParseStatement(TCustomSqlStatement psql) { 307 if (rootNode == null) return -1; 308 super.doParseStatement(psql); 309 310 TExecImmeNode execImmeNode = (TExecImmeNode)rootNode; 311 this.bindArguments = execImmeNode.getBindArguments(); 312 this.intoVariables = execImmeNode.getIntoVariables(); 313 this.dynamicStringExpr = execImmeNode.getDynamicStringExpr(); 314 this.returnNames = execImmeNode.getReturnNames(); 315 316 this.dynamicStringExpr.evaluate(this.getFrameStack(),this); 317 this.dynamicSQL = this.dynamicStringExpr.getPlainText(); 318 319 //TODO parse the dynamicSQL, 先不在这里解析动态 SQL, 考虑和 TASTEvaluator 结合 320 // getDynamicStatements(); 321 322 return 0; 323 } 324 325 public void accept(TParseTreeVisitor v){ 326 v.preVisit(this); 327 v.postVisit(this); 328 } 329 330 public void acceptChildren(TParseTreeVisitor v){ 331 v.preVisit(this); 332 this.dynamicStringExpr.acceptChildren(v); 333 v.postVisit(this); 334 } 335 336 public void setDynamicStringExpr(TExpression dynamicStringExpr) { 337 this.dynamicStringExpr = dynamicStringExpr; 338 } 339 340 public void setBindArguments(TBindArgumentList bindArguments) { 341 this.bindArguments = bindArguments; 342 } 343 344 public void setIntoVariables(TExpressionList intoVariables) { 345 this.intoVariables = intoVariables; 346 } 347 348 public void setReturnNames(TObjectNameList returnNames) { 349 this.returnNames = returnNames; 350 } 351 352 public void setDynamicSQL(String dynamicSQL) { 353 this.dynamicSQL = dynamicSQL; 354 } 355 356 public void setDynamicStatements(TStatementList dynamicStatements) { 357 this.dynamicStatements = dynamicStatements; 358 } 359 360// private String EvaluateExpr(){ 361// if (this.dynamicStringExpr == null) return ""; 362// return this.dynamicStringExpr.getPlainText(); 363// } 364} 365 366//class calculateExprVisitor implements IExpressionVisitor { 367// Stack<TExpression> expressionStack = new Stack<>(); 368// 369// public boolean exprVisit(TParseTreeNode pNode,boolean isLeafNode){ 370// if (isLeafNode){ 371// expressionStack.push((TExpression)pNode); 372// } 373// 374// TExpression expr = (TExpression)pNode; 375// switch (expr.getExpressionType()){ 376// case concatenate_t: 377// TExpression expr1 = expressionStack.pop(); 378// TExpression expr2 = expressionStack.pop(); 379// 380// String expr1Str = expr1.getPlainText(); 381// String expr2Str = expr2.getPlainText(); 382// switch (expr1.getExpressionType()){ 383// case simple_constant_t: 384// expr1Str = TBaseType.getStringInsideLiteral(expr1Str); 385// break; 386// case function_t: 387// expr1Str = expr1.getFunctionCall().getFunctionName().toString(); 388// break; 389// case simple_object_name_t: 390// expr1Str = expr1Str.replace(".","_"); 391// break; 392// default: 393// break; 394// } 395// 396// switch (expr2.getExpressionType()){ 397// case simple_constant_t: 398// expr2Str = TBaseType.getStringInsideLiteral(expr2Str); 399// break; 400// case function_t: 401// expr2Str = expr2.getFunctionCall().getFunctionName().toString(); 402// break; 403// case simple_object_name_t: 404// expr2Str = expr2Str.replace(".","_"); 405// break; 406// default: 407// break; 408// } 409// 410// 411// //TExpression expr3 = expressionStack.peek(); 412// ((TExpression)pNode).setPlainText(expr2Str+expr1Str); 413// 414// expressionStack.push((TExpression)pNode); 415// 416// break; 417// } 418// return true; 419// }; 420// 421//} 422