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                        //A whitespace-only or comment-only block parses with
130                        //rc 0 and ZERO statements (mantis 4681) — guard get(0).
131                        if (ret == 0 && parser.sqlstatements.size() > 0){
132                            stmt = parser.sqlstatements.get(0);
133                        }else if (ret != 0){
134                            for(int j=0;j<parser.getErrorCount();j++){
135                                this.parseerrormessagehandle(parser.getSyntaxErrors().get(j));
136                            }
137                        }
138                    } else if (tokenStr.startsWith("'") && tokenStr.endsWith("'")){
139                        // Single-quoted string literal - extract SQL and parse it
140                        String sqlContent = TBaseType.getStringInsideLiteral(tokenStr);
141                        if (sqlContent != null && !sqlContent.isEmpty()){
142                            TGSqlParser parser = new TGSqlParser(EDbVendor.dbvsnowflake);
143                            parser.sqltext = TBaseType.stringBlock((int)st.lineNo - 1,(int)st.columnNo + 1) + sqlContent;
144                            int ret = parser.parse();
145                            if (ret == 0 && parser.sqlstatements.size() > 0){
146                                stmt = parser.sqlstatements.get(0);
147                            }
148                            // Silently ignore parse errors for dynamic SQL - it may contain placeholders
149                        }
150                    }
151                    // Variable references (e.g., :SQLStmt) are resolved lazily in getStmt()
152                }
153                break;
154            case dbvnetezza:
155                this.moduleName = sqlNode.getModuleName();
156                this.statementName = sqlNode.getModuleName();
157                this.parameters = sqlNode.getStringValues();
158                preparedValue = sqlNode.getPreparedValue();
159                if (preparedValue != null){
160                    preparedValue.doParse(this, ESqlClause.unknown);
161                }
162
163                switch (executeType){
164                    case expr:
165                        if (sqlNode.getPreparedValue().getExpressionType() == EExpressionType.function_t){
166                            TFunctionCall functionCall = sqlNode.getPreparedValue().getFunctionCall();
167                            this.moduleName = functionCall.getFunctionName();
168                            this.parameters = functionCall.getArgs();
169                            this.executeType = EExecType.module;
170                        }
171                        break;
172                    default:
173                        break;
174                }
175
176                break;
177            case dbvredshift:
178                this.moduleName = sqlNode.getModuleName();
179                this.statementName = sqlNode.getModuleName();
180                this.parameters = sqlNode.getStringValues();
181                preparedValue = sqlNode.getPreparedValue();
182                if (preparedValue != null){
183                    preparedValue.doParse(this, ESqlClause.unknown);
184
185                    if (this.preparedValue.getExpressionType() == EExpressionType.simple_object_name_t){
186                        TObjectName var = this.preparedValue.getObjectOperand();
187                        //  if (var.getDbObjectType() == EDbObjectType.variable){
188                        TVariable symbolVariable =  TSymbolTableManager.searchSymbolVariable(this.getFrameStack(),var.toString());
189                        if (symbolVariable != null){
190                            this.sqlText = symbolVariable.getVariableStr();
191                            //System.out.println(this.sqlText);
192                        }
193                        // }
194                    }
195
196                }
197                break;
198            default:
199                this.moduleName = sqlNode.getModuleName();
200                this.statementName = sqlNode.getModuleName();
201                this.parameters = sqlNode.getStringValues();
202                preparedValue = sqlNode.getPreparedValue();
203                if (preparedValue != null){
204                    preparedValue.doParse(this, ESqlClause.unknown);
205                }
206
207                // available in TMssqlExecute currently, maybe will be moved to here in later version
208                //this.stringValues = sqlNode.getStringValues();
209                //this.linkServerName = sqlNode.getLinkServerName();
210
211                break;
212        }
213        return 0;
214    }
215
216    private static final Object DYNAMIC_PARSE_LOCK = new Object();
217
218    private TStatementList dynamicStatements = null;
219
220    /**
221     *
222     * @return sql statement instance that generated dynamically based on {@link #sqlText}
223     */
224    public TStatementList getDynamicStatements() {
225        if (this.dynamicStatements != null) return this.dynamicStatements;
226        if (this.sqlText == null) return null;
227
228        String query = this.sqlText;
229        if((this.preparedValue != null) && (this.preparedValue.getPlainTextLineNo() != -1)){
230            long lineNo = this.preparedValue.getPlainTextLineNo();
231            long columnNo = this.preparedValue.getPlainTextColumnNo();
232            query = TBaseType.stringBlock((int)lineNo - 1,(int)columnNo)+ this.sqlText;
233        }
234
235        // Use a local parser instance for thread safety instead of a shared static parser
236        TGSqlParser localParser = new TGSqlParser(EDbVendor.dbvredshift);
237        localParser.sqltext = query;
238        int ret = localParser.parse();
239
240        if ( ret != 0){
241            for(int j=0;j<localParser.getErrorCount();j++){
242                this.parseerrormessagehandle(localParser.getSyntaxErrors().get(j));
243            }
244
245            return null;
246        }
247
248        this.dynamicStatements = new TStatementList();
249        for(int i=0;i<localParser.sqlstatements.size();i++){
250            if (this.getParentStmt() == null){
251                localParser.sqlstatements.get(i).setParentStmt(this);
252            }else{
253                localParser.sqlstatements.get(i).setParentStmt(this.getParentStmt());
254            }
255
256            this.dynamicStatements.add(localParser.sqlstatements.get(i));
257        }
258        return dynamicStatements;
259    }
260
261    public TObjectName getLinkServerName() {
262        return linkServerName;
263    }
264
265    // GaussDB: EXECUTE DIRECT ON (nodename_list) 'sql'
266    private TObjectNameList directOnNodes;
267
268    public TObjectNameList getDirectOnNodes() {
269        return directOnNodes;
270    }
271
272    private TObjectName linkServerName;
273    private TExpressionList stringValues = null;
274
275    public TExpressionList getStringValues() {
276        return stringValues;
277    }
278
279    private TObjectName moduleName;
280
281    public TObjectName getModuleName() {
282        return moduleName;
283    }
284
285    private TExpression stmtString;
286    private TObjectName intoVariable;
287    private TExpressionList usingVariables;
288
289    private TObjectName statementName;
290    private TExpressionList parameters;
291    private TExpression preparedValue;//couchbase
292
293    public void setSqlText(String sqlText) {
294        this.sqlText = sqlText;
295    }
296
297    private String sqlText = null;
298
299    public String getSqlText() {
300        return sqlText;
301    }
302
303    public void init(Object arg1){
304        stmtString = (TExpression)arg1;
305        sqlText = stmtString.toString();
306        switch (stmtString.getExpressionType()){
307            case simple_object_name_t:
308                moduleName = stmtString.getObjectOperand();
309                //moduleName.setObjectType(TObjectName.ttobjProcedureName);
310                moduleName.setDbObjectType(EDbObjectType.procedure);
311                break;
312            case function_t:
313                if (stmtString.toString().startsWith("format")){
314                    //postgresql format function
315                    TFunctionCall functionCall = stmtString.getFunctionCall();
316                    sqlText = functionCall.getArgs().getExpression(0).toString();
317                    sqlText = sqlText.replaceAll("%s","PLACEHOLDER");
318                }
319                break;
320        }
321
322    }
323    public void init(Object arg1,Object arg2){
324        init(arg1);
325        intoVariable = (TObjectName)arg2;
326
327    }
328
329    public TObjectName getIntoVariable() {
330        return intoVariable;
331    }
332
333    public TExpression getStmtString() {
334        return stmtString;
335    }
336
337    public TExpressionList getUsingVariables() {
338        return usingVariables;
339    }
340
341    public void init(Object arg1,Object arg2, Object arg3){
342        init(arg1,arg2);
343        usingVariables = (TExpressionList)arg3;
344
345    }
346
347    public void accept(TParseTreeVisitor v){
348        v.preVisit(this);
349        v.postVisit(this);
350    }
351
352    public void acceptChildren(TParseTreeVisitor v){
353        v.preVisit(this);
354        v.postVisit(this);
355    }
356
357    public void setStmtString(TExpression stmtString) {
358        this.stmtString = stmtString;
359    }
360
361    public void setIntoVariable(TObjectName intoVariable) {
362        this.intoVariable = intoVariable;
363    }
364
365    public void setUsingVariables(TExpressionList usingVariables) {
366        this.usingVariables = usingVariables;
367    }
368
369    public TObjectName getStatementName() {
370        return statementName;
371    }
372    public TExpressionList getParameters() {
373        return parameters;
374    }
375    public void setStatementName(TObjectName statementName) {
376        this.statementName = statementName;
377    }
378    public void setParameters(TExpressionList parameters) {
379        this.parameters = parameters;
380    }
381    public TExpression getPreparedValue() {
382        return preparedValue;
383    }
384
385    /**
386     * Search for a variable assignment in the parent block statements.
387     * Used by Snowflake EXECUTE IMMEDIATE to resolve variable references.
388     *
389     * @param varName the variable name to search for
390     * @return the SQL string assigned to the variable, or null if not found
391     */
392    private String findVariableAssignmentValue(String varName) {
393        TCustomSqlStatement parent = this.getParentStmt();
394        // Debug output
395        // System.out.println("[DEBUG] findVariableAssignmentValue: varName=" + varName + ", parent=" + (parent != null ? parent.getClass().getSimpleName() : "null"));
396        if (parent == null) return null;
397
398        TStatementList bodyStmts = null;
399
400        // Get body statements from parent - check multiple levels
401        TCustomSqlStatement current = parent;
402        while (current != null && bodyStmts == null) {
403            if (current instanceof TBlockSqlStatement) {
404                bodyStmts = ((TBlockSqlStatement) current).getBodyStatements();
405            } else if (current instanceof TCreateProcedureStmt) {
406                bodyStmts = ((TCreateProcedureStmt) current).getBodyStatements();
407            }
408            if (bodyStmts == null || bodyStmts.size() == 0) {
409                bodyStmts = null;
410                current = current.getParentStmt();
411            }
412        }
413
414        // System.out.println("[DEBUG] bodyStmts=" + (bodyStmts != null ? bodyStmts.size() : "null"));
415
416        if (bodyStmts == null) return null;
417
418        // Search backwards through statements to find the most recent assignment
419        for (int i = bodyStmts.size() - 1; i >= 0; i--) {
420            TCustomSqlStatement bodyStmt = bodyStmts.get(i);
421            // System.out.println("[DEBUG] bodyStmt[" + i + "]=" + bodyStmt.getClass().getSimpleName());
422            if (bodyStmt instanceof TAssignStmt) {
423                TAssignStmt assignStmt = (TAssignStmt) bodyStmt;
424                TExpression leftExpr = assignStmt.getLeft();
425                if (leftExpr != null) {
426                    String leftName = leftExpr.toString();
427                    // Handle colon prefix if present
428                    if (leftName.startsWith(":")) {
429                        leftName = leftName.substring(1);
430                    }
431                    // System.out.println("[DEBUG] checking assignment: leftName=" + leftName + ", varName=" + varName);
432                    if (leftName.equalsIgnoreCase(varName)) {
433                        // Found the assignment - extract the SQL content
434                        TExpression rightExpr = assignStmt.getExpression();
435                        if (rightExpr != null) {
436                            String value = rightExpr.toString();
437                            // System.out.println("[DEBUG] found assignment: value=" + value);
438                            // If it's a string literal, extract the content
439                            if (value.startsWith("'") && value.endsWith("'")) {
440                                return TBaseType.getStringInsideLiteral(value);
441                            } else if (value.startsWith("$$") && value.endsWith("$$")) {
442                                return TBaseType.getStringInsideLiteral(value);
443                            }
444                            return value;
445                        }
446                    }
447                }
448            }
449        }
450        return null;
451    }
452
453}