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}