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