From 3e5409f4ce4560e62a58f2dd90751b13c8fb108d Mon Sep 17 00:00:00 2001 From: Yash Khare Date: Wed, 27 May 2026 16:29:06 +0530 Subject: [PATCH] feat(spring-mysql-redis): add endpoint to trigger COM_STMT_RESET Adds GET /api/todos/stmt-reset/{id}/{n} which re-executes a server-side prepared statement n times on the same connection. Combined with the new JDBC URL flags (useServerPrepStmts, cachePrepStmts, useCursorFetch), MySQL Connector/J 8.x emits COM_STMT_RESET between re-executions, which exercises the synthetic-OK fallback path added in keploy/keploy#4217 so record/replay can be validated. Co-Authored-By: Claude Opus 4.6 --- .../todo/controller/TodoController.java | 55 +++++++++++++++++++ .../src/main/resources/application.properties | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/spring-mysql-redis/src/main/java/org/example/todo/controller/TodoController.java b/spring-mysql-redis/src/main/java/org/example/todo/controller/TodoController.java index ad0c3b92..c7e1dd25 100644 --- a/spring-mysql-redis/src/main/java/org/example/todo/controller/TodoController.java +++ b/spring-mysql-redis/src/main/java/org/example/todo/controller/TodoController.java @@ -6,7 +6,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; @RestController @@ -16,6 +24,9 @@ public class TodoController { @Autowired private TodoService todoService; + @Autowired + private DataSource dataSource; + @GetMapping public List getAllTodos() { return todoService.findAll(); @@ -52,4 +63,48 @@ public ResponseEntity deleteTodo(@PathVariable Long id) { todoService.deleteById(id); return ResponseEntity.ok().build(); } + + /** + * Re-executes a server-side prepared statement {@code n} times on the same + * connection. With {@code useServerPrepStmts=true} and + * {@code useCursorFetch=true} (set in application.properties) plus a + * non-zero fetchSize, MySQL Connector/J 8.x sends a COM_STMT_RESET packet + * between re-executions to clear the cursor state. This endpoint exists to + * exercise that code path so Keploy record/replay can capture and match + * COM_STMT_RESET frames. + */ + @GetMapping("/stmt-reset/{id}/{n}") + public ResponseEntity> stmtReset(@PathVariable Long id, + @PathVariable int n) throws SQLException { + List> rows = new ArrayList<>(); + String sql = "SELECT id, title, description, completed FROM todo WHERE id = ?"; + + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + // Forces server-side cursor (with useCursorFetch=true), which is + // what causes Connector/J to emit COM_STMT_RESET on re-execution. + ps.setFetchSize(1); + + for (int i = 0; i < n; i++) { + ps.setLong(1, id); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + Map row = new HashMap<>(); + row.put("id", rs.getLong("id")); + row.put("title", rs.getString("title")); + row.put("description", rs.getString("description")); + row.put("completed", rs.getBoolean("completed")); + rows.add(row); + } + } + } + } + + Map response = new HashMap<>(); + response.put("iterations", n); + response.put("rowsFetched", rows.size()); + response.put("results", rows); + return ResponseEntity.ok(response); + } } diff --git a/spring-mysql-redis/src/main/resources/application.properties b/spring-mysql-redis/src/main/resources/application.properties index 776a0c97..e73bd8dc 100644 --- a/spring-mysql-redis/src/main/resources/application.properties +++ b/spring-mysql-redis/src/main/resources/application.properties @@ -1,5 +1,5 @@ # MySQL Configuration -spring.datasource.url=jdbc:mysql://localhost:3306/todoapp?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true +spring.datasource.url=jdbc:mysql://localhost:3306/todoapp?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true&useServerPrepStmts=true&cachePrepStmts=true&useCursorFetch=true spring.datasource.username=yourusername spring.datasource.password=yourpassword spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver