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