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