001package gudusoft.gsqlparser.stmt;
002
003import gudusoft.gsqlparser.*;
004import gudusoft.gsqlparser.compiler.TSymbolTableManager;
005import gudusoft.gsqlparser.compiler.TVariable;
006import gudusoft.gsqlparser.nodes.*;
007
008
009/**
010 * execute statement
011 *
012 * db: couchbase, netezza,greenplum,mysql, postgresql,redshift
013 *
014 * @TODO: 2024/2/7  {@link  gudusoft.gsqlparser.stmt.mssql.TMssqlExecute}, {@link TExecImmeStmt} should merge into this class
015 */
016public class TExecuteSqlStatement extends TCustomSqlStatement {
017
018    private String preparedSqlText;
019
020    public void setPreparedSqlText(String preparedSqlText) {
021        this.preparedSqlText = preparedSqlText;
022    }
023
024    public String getPreparedSqlText() {
025        return preparedSqlText;
026    }
027
028    private EExecType executeType = EExecType.unknown;
029
030    public EExecType getExecuteType() {
031        return executeType;
032    }
033
034    private TCustomSqlStatement stmt;
035    private boolean variableResolutionAttempted = false;
036
037    public TCustomSqlStatement getStmt() {
038        // For Snowflake, try lazy resolution of variable references
039        if (stmt == null && !variableResolutionAttempted && dbvendor == EDbVendor.dbvsnowflake) {
040            variableResolutionAttempted = true;
041            if (stmtString != null && stmtString.getExpressionType() == EExpressionType.simple_object_name_t) {
042                String tokenStr = stmtString.getStartToken().toString();
043                String varName = tokenStr;
044                if (varName.startsWith(":")) {
045                    varName = varName.substring(1);
046                }
047                String sqlContent = findVariableAssignmentValue(varName);
048                if (sqlContent != null && !sqlContent.isEmpty()) {
049                    TGSqlParser parser = new TGSqlParser(EDbVendor.dbvsnowflake);
050                    parser.sqltext = sqlContent;
051                    int ret = parser.parse();
052                    if (ret == 0 && parser.sqlstatements.size() > 0) {
053                        stmt = parser.sqlstatements.get(0);
054                    }
055                }
056            }
057        }
058        return stmt;
059    }
060
061    public TExecuteSqlStatement(){
062        this(EDbVendor.dbvpostgresql);
063    }
064
065    public TExecuteSqlStatement(EDbVendor dbvendor) {
066        super(dbvendor);
067        sqlstatementtype = ESqlStatementType.sstExecute;
068    }
069
070    public int doParseStatement(TCustomSqlStatement psql) {
071
072        if (rootNode == null) return -1;
073        super.doParseStatement(psql);
074
075        TExecuteSqlNode sqlNode = (TExecuteSqlNode)rootNode;
076        this.executeType = sqlNode.getExecuteType();
077
078        switch (dbvendor){
079            case dbvgaussdb:
080                if (this.executeType == EExecType.direct) {
081                    // EXECUTE DIRECT ON (nodename_list) 'sql_string'
082                    this.directOnNodes = sqlNode.getDirectOnNodes();
083                    this.stmtString = sqlNode.getStmtString();
084                    if (this.stmtString != null) {
085                        sqlText = stmtString.toString();
086                    }
087                    break;
088                }
089                // Fall through to postgresql handling for regular EXECUTE
090            case dbvpostgresql:
091                this.stmtString = sqlNode.getStmtString();
092                this.intoVariable = sqlNode.getIntoVariable();
093                this.usingVariables = sqlNode.getUsingVariables();
094
095                sqlText = stmtString.toString();
096                if (stmtString.getExpressionType() == EExpressionType.function_t){
097                    if (stmtString.toString().startsWith("format")){
098                        //postgresql format function
099                        TFunctionCall functionCall = stmtString.getFunctionCall();
100                        sqlText = functionCall.getArgs().getExpression(0).toString();
101                        sqlText = sqlText.replaceAll("%s","PLACEHOLDER");
102                    }
103                }
104               // System.out.println(sqlText);
105
106                this.moduleName = sqlNode.getModuleName();
107                this.statementName = sqlNode.getModuleName();
108                this.parameters = sqlNode.getStringValues();
109
110                break;
111            case dbvsnowflake:
112                if (this.executeType == EExecType.task) {
113                    // EXECUTE TASK statement - just store the task name
114                    this.moduleName = sqlNode.getModuleName();
115                } else if (this.executeType == EExecType.from_stage) {
116                    // EXECUTE IMMEDIATE FROM @stage/path or FROM 'path'
117                    // Nothing to resolve - the source is a file reference
118                } else {
119                    // EXECUTE IMMEDIATE statement
120                    this.stmtString = sqlNode.getStmtString();
121                    //System.out.println(stmtString.toString());
122                    TSourceToken st = stmtString.getStartToken();
123                    String tokenStr = st.toString();
124                    if (tokenStr.startsWith("$$")){
125                        // Dollar-quoted string
126                        TGSqlParser parser = new TGSqlParser(EDbVendor.dbvsnowflake);
127                        parser.sqltext = TBaseType.stringBlock((int)st.lineNo - 1,(int)st.columnNo)+ TBaseType.getStringInsideLiteral(tokenStr);
128                        int ret = parser.parse();
129                        if (ret == 0){
130                            stmt = parser.sqlstatements.get(0);
131                        }else{
132                            for(int j=0;j<parser.getErrorCount();j++){
133                                this.parseerrormessagehandle(parser.getSyntaxErrors().get(j));
134                            }
135                        }
136                    } else if (tokenStr.startsWith("'") && tokenStr.endsWith("'")){
137                        // Single-quoted string literal - extract SQL and parse it
138                        String sqlContent = TBaseType.getStringInsideLiteral(tokenStr);
139                        if (sqlContent != null && !sqlContent.isEmpty()){
140                            TGSqlParser parser = new TGSqlParser(EDbVendor.dbvsnowflake);
141                            parser.sqltext = TBaseType.stringBlock((int)st.lineNo - 1,(int)st.columnNo + 1) + sqlContent;
142                            int ret = parser.parse();
143                            if (ret == 0 && parser.sqlstatements.size() > 0){
144                                stmt = parser.sqlstatements.get(0);
145                            }
146                            // Silently ignore parse errors for dynamic SQL - it may contain placeholders
147                        }
148                    }
149                    // Variable references (e.g., :SQLStmt) are resolved lazily in getStmt()
150                }
151                break;
152            case dbvnetezza:
153                this.moduleName = sqlNode.getModuleName();
154                this.statementName = sqlNode.getModuleName();
155                this.parameters = sqlNode.getStringValues();
156                preparedValue = sqlNode.getPreparedValue();
157                if (preparedValue != null){
158                    preparedValue.doParse(this, ESqlClause.unknown);
159                }
160
161                switch (executeType){
162                    case expr:
163                        if (sqlNode.getPreparedValue().getExpressionType() == EExpressionType.function_t){
164                            TFunctionCall functionCall = sqlNode.getPreparedValue().getFunctionCall();
165                            this.moduleName = functionCall.getFunctionName();
166                            this.parameters = functionCall.getArgs();
167                            this.executeType = EExecType.module;
168                        }
169                        break;
170                    default:
171                        break;
172                }
173
174                break;
175            case dbvredshift:
176                this.moduleName = sqlNode.getModuleName();
177                this.statementName = sqlNode.getModuleName();
178                this.parameters = sqlNode.getStringValues();
179                preparedValue = sqlNode.getPreparedValue();
180                if (preparedValue != null){
181                    preparedValue.doParse(this, ESqlClause.unknown);
182
183                    if (this.preparedValue.getExpressionType() == EExpressionType.simple_object_name_t){
184                        TObjectName var = this.preparedValue.getObjectOperand();
185                        //  if (var.getDbObjectType() == EDbObjectType.variable){
186                        TVariable symbolVariable =  TSymbolTableManager.searchSymbolVariable(this.getFrameStack(),var.toString());
187                        if (symbolVariable != null){
188                            this.sqlText = symbolVariable.getVariableStr();
189                            //System.out.println(this.sqlText);
190                        }
191                        // }
192                    }
193
194                }
195                break;
196            default:
197                this.moduleName = sqlNode.getModuleName();
198                this.statementName = sqlNode.getModuleName();
199                this.parameters = sqlNode.getStringValues();
200                preparedValue = sqlNode.getPreparedValue();
201                if (preparedValue != null){
202                    preparedValue.doParse(this, ESqlClause.unknown);
203                }
204
205                // available in TMssqlExecute currently, maybe will be moved to here in later version
206                //this.stringValues = sqlNode.getStringValues();
207                //this.linkServerName = sqlNode.getLinkServerName();
208
209                break;
210        }
211        return 0;
212    }
213
214    private static final Object DYNAMIC_PARSE_LOCK = new Object();
215
216    private TStatementList dynamicStatements = null;
217
218    /**
219     *
220     * @return sql statement instance that generated dynamically based on {@link #sqlText}
221     */
222    public TStatementList getDynamicStatements() {
223        if (this.dynamicStatements != null) return this.dynamicStatements;
224        if (this.sqlText == null) return null;
225
226        String query = this.sqlText;
227        if((this.preparedValue != null) && (this.preparedValue.getPlainTextLineNo() != -1)){
228            long lineNo = this.preparedValue.getPlainTextLineNo();
229            long columnNo = this.preparedValue.getPlainTextColumnNo();
230            query = TBaseType.stringBlock((int)lineNo - 1,(int)columnNo)+ this.sqlText;
231        }
232
233        // Use a local parser instance for thread safety instead of a shared static parser
234        TGSqlParser localParser = new TGSqlParser(EDbVendor.dbvredshift);
235        localParser.sqltext = query;
236        int ret = localParser.parse();
237
238        if ( ret != 0){
239            for(int j=0;j<localParser.getErrorCount();j++){
240                this.parseerrormessagehandle(localParser.getSyntaxErrors().get(j));
241            }
242
243            return null;
244        }
245
246        this.dynamicStatements = new TStatementList();
247        for(int i=0;i<localParser.sqlstatements.size();i++){
248            if (this.getParentStmt() == null){
249                localParser.sqlstatements.get(i).setParentStmt(this);
250            }else{
251                localParser.sqlstatements.get(i).setParentStmt(this.getParentStmt());
252            }
253
254            this.dynamicStatements.add(localParser.sqlstatements.get(i));
255        }
256        return dynamicStatements;
257    }
258
259    public TObjectName getLinkServerName() {
260        return linkServerName;
261    }
262
263    // GaussDB: EXECUTE DIRECT ON (nodename_list) 'sql'
264    private TObjectNameList directOnNodes;
265
266    public TObjectNameList getDirectOnNodes() {
267        return directOnNodes;
268    }
269
270    private TObjectName linkServerName;
271    private TExpressionList stringValues = null;
272
273    public TExpressionList getStringValues() {
274        return stringValues;
275    }
276
277    private TObjectName moduleName;
278
279    public TObjectName getModuleName() {
280        return moduleName;
281    }
282
283    private TExpression stmtString;
284    private TObjectName intoVariable;
285    private TExpressionList usingVariables;
286
287    private TObjectName statementName;
288    private TExpressionList parameters;
289    private TExpression preparedValue;//couchbase
290
291    public void setSqlText(String sqlText) {
292        this.sqlText = sqlText;
293    }
294
295    private String sqlText = null;
296
297    public String getSqlText() {
298        return sqlText;
299    }
300
301    public void init(Object arg1){
302        stmtString = (TExpression)arg1;
303        sqlText = stmtString.toString();
304        switch (stmtString.getExpressionType()){
305            case simple_object_name_t:
306                moduleName = stmtString.getObjectOperand();
307                //moduleName.setObjectType(TObjectName.ttobjProcedureName);
308                moduleName.setDbObjectType(EDbObjectType.procedure);
309                break;
310            case function_t:
311                if (stmtString.toString().startsWith("format")){
312                    //postgresql format function
313                    TFunctionCall functionCall = stmtString.getFunctionCall();
314                    sqlText = functionCall.getArgs().getExpression(0).toString();
315                    sqlText = sqlText.replaceAll("%s","PLACEHOLDER");
316                }
317                break;
318        }
319
320    }
321    public void init(Object arg1,Object arg2){
322        init(arg1);
323        intoVariable = (TObjectName)arg2;
324
325    }
326
327    public TObjectName getIntoVariable() {
328        return intoVariable;
329    }
330
331    public TExpression getStmtString() {
332        return stmtString;
333    }
334
335    public TExpressionList getUsingVariables() {
336        return usingVariables;
337    }
338
339    public void init(Object arg1,Object arg2, Object arg3){
340        init(arg1,arg2);
341        usingVariables = (TExpressionList)arg3;
342
343    }
344
345    public void accept(TParseTreeVisitor v){
346        v.preVisit(this);
347        v.postVisit(this);
348    }
349
350    public void acceptChildren(TParseTreeVisitor v){
351        v.preVisit(this);
352        v.postVisit(this);
353    }
354
355    public void setStmtString(TExpression stmtString) {
356        this.stmtString = stmtString;
357    }
358
359    public void setIntoVariable(TObjectName intoVariable) {
360        this.intoVariable = intoVariable;
361    }
362
363    public void setUsingVariables(TExpressionList usingVariables) {
364        this.usingVariables = usingVariables;
365    }
366
367    public TObjectName getStatementName() {
368        return statementName;
369    }
370    public TExpressionList getParameters() {
371        return parameters;
372    }
373    public void setStatementName(TObjectName statementName) {
374        this.statementName = statementName;
375    }
376    public void setParameters(TExpressionList parameters) {
377        this.parameters = parameters;
378    }
379    public TExpression getPreparedValue() {
380        return preparedValue;
381    }
382
383    /**
384     * Search for a variable assignment in the parent block statements.
385     * Used by Snowflake EXECUTE IMMEDIATE to resolve variable references.
386     *
387     * @param varName the variable name to search for
388     * @return the SQL string assigned to the variable, or null if not found
389     */
390    private String findVariableAssignmentValue(String varName) {
391        TCustomSqlStatement parent = this.getParentStmt();
392        // Debug output
393        // System.out.println("[DEBUG] findVariableAssignmentValue: varName=" + varName + ", parent=" + (parent != null ? parent.getClass().getSimpleName() : "null"));
394        if (parent == null) return null;
395
396        TStatementList bodyStmts = null;
397
398        // Get body statements from parent - check multiple levels
399        TCustomSqlStatement current = parent;
400        while (current != null && bodyStmts == null) {
401            if (current instanceof TBlockSqlStatement) {
402                bodyStmts = ((TBlockSqlStatement) current).getBodyStatements();
403            } else if (current instanceof TCreateProcedureStmt) {
404                bodyStmts = ((TCreateProcedureStmt) current).getBodyStatements();
405            }
406            if (bodyStmts == null || bodyStmts.size() == 0) {
407                bodyStmts = null;
408                current = current.getParentStmt();
409            }
410        }
411
412        // System.out.println("[DEBUG] bodyStmts=" + (bodyStmts != null ? bodyStmts.size() : "null"));
413
414        if (bodyStmts == null) return null;
415
416        // Search backwards through statements to find the most recent assignment
417        for (int i = bodyStmts.size() - 1; i >= 0; i--) {
418            TCustomSqlStatement bodyStmt = bodyStmts.get(i);
419            // System.out.println("[DEBUG] bodyStmt[" + i + "]=" + bodyStmt.getClass().getSimpleName());
420            if (bodyStmt instanceof TAssignStmt) {
421                TAssignStmt assignStmt = (TAssignStmt) bodyStmt;
422                TExpression leftExpr = assignStmt.getLeft();
423                if (leftExpr != null) {
424                    String leftName = leftExpr.toString();
425                    // Handle colon prefix if present
426                    if (leftName.startsWith(":")) {
427                        leftName = leftName.substring(1);
428                    }
429                    // System.out.println("[DEBUG] checking assignment: leftName=" + leftName + ", varName=" + varName);
430                    if (leftName.equalsIgnoreCase(varName)) {
431                        // Found the assignment - extract the SQL content
432                        TExpression rightExpr = assignStmt.getExpression();
433                        if (rightExpr != null) {
434                            String value = rightExpr.toString();
435                            // System.out.println("[DEBUG] found assignment: value=" + value);
436                            // If it's a string literal, extract the content
437                            if (value.startsWith("'") && value.endsWith("'")) {
438                                return TBaseType.getStringInsideLiteral(value);
439                            } else if (value.startsWith("$$") && value.endsWith("$$")) {
440                                return TBaseType.getStringInsideLiteral(value);
441                            }
442                            return value;
443                        }
444                    }
445                }
446            }
447        }
448        return null;
449    }
450
451}