Skip to content

Commit 6a39b6e

Browse files
authored
Fix PreparedStatement getGeneratedKeys() failure with insert triggers (#2740) (#2742)
* Fix PreparedStatement getGeneratedKeys() failure with triggers (#2740) * added test case
1 parent 2ca55b8 commit 6a39b6e

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,11 +792,14 @@ boolean onRetValue(TDSReader tdsReader) throws SQLServerException {
792792
* Override TDS token processing behavior for PreparedStatement.
793793
* For regular Statement, the execute API for INSERT requires reading an additional explicit
794794
* TDS_DONE token that contains the actual update count returned by the server.
795-
* PreparedStatement does not require this additional token processing.
795+
* PreparedStatement does not require this additional token processing, unless
796+
* generated keys were requested (which requires processing additional TDS tokens).
796797
*/
797798
@Override
798799
protected boolean hasUpdateCountTDSTokenForInsertCmd() {
799-
return false;
800+
// When generated keys are requested, we need to process additional TDS tokens
801+
// to properly locate the ResultSet containing the generated keys
802+
return bRequestedGeneratedKeys;
800803
}
801804

802805
/**

src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/StatementTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2797,6 +2797,31 @@ public void testPrepStmtExecuteUpdateInsertAndGenKeys() {
27972797
}
27982798
}
27992799

2800+
/**
2801+
* Tests execute using PreparedStatement for Insert followed by getGenerateKeys
2802+
*
2803+
* @throws Exception
2804+
*/
2805+
@Test
2806+
public void testPrepStmtExecuteInsertAndGenKeys() {
2807+
try (Connection con = getConnection()) {
2808+
String sql = "INSERT INTO " + tableName + " (NAME) VALUES('test');";
2809+
try(PreparedStatement stmt = con.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS)) {
2810+
stmt.execute();
2811+
int updateCount = stmt.getUpdateCount();
2812+
assertEquals(updateCount, 1, "updateCount should have been 1, but received : " + updateCount);
2813+
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
2814+
if (generatedKeys.next()) {
2815+
int id = generatedKeys.getInt(1);
2816+
assertEquals(id, 4, "id should have been 4, but received : " + id);
2817+
}
2818+
}
2819+
}
2820+
} catch (SQLException e) {
2821+
fail(TestResource.getResource("R_unexpectedException") + e.getMessage());
2822+
}
2823+
}
2824+
28002825
/**
28012826
* Tests executeUpdate using PreparedStatement for Insert followed by getGenerateKeys
28022827
*
@@ -3324,6 +3349,62 @@ public void testPreparedStatementExecuteDelAndSelect() throws SQLException {
33243349
}
33253350
}
33263351

3352+
/**
3353+
* Tests PreparedStatement with triggers and generated keys to validate PR #2742 fix.
3354+
* This test validates that both update counts work correctly AND getGeneratedKeys()
3355+
* works when triggers are involved.
3356+
*
3357+
* @throws SQLException
3358+
*/
3359+
@Test
3360+
public void testPreparedStatementWithTriggersAndGeneratedKeys() throws SQLException {
3361+
// Create separate test tables to avoid conflicts with existing setup
3362+
String testTableA = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("TriggerTestTableA"));
3363+
String testTableB = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("TriggerTestTableB"));
3364+
String testTrigger = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("TriggerTestTrigger"));
3365+
3366+
try (Connection conn = getConnection();
3367+
Statement stmt = conn.createStatement()) {
3368+
3369+
// Cleanup any existing objects
3370+
TestUtils.dropTriggerIfExists(testTrigger, stmt);
3371+
TestUtils.dropTableIfExists(testTableB, stmt);
3372+
TestUtils.dropTableIfExists(testTableA, stmt);
3373+
3374+
// Create schema
3375+
stmt.executeUpdate("CREATE TABLE " + testTableA + " (ID int NOT NULL IDENTITY(1,1) PRIMARY KEY, NAME varchar(32))");
3376+
stmt.executeUpdate("CREATE TABLE " + testTableB + " (ID int NOT NULL IDENTITY(1,1) PRIMARY KEY)");
3377+
stmt.executeUpdate("CREATE TRIGGER " + testTrigger + " ON " + testTableA + " FOR INSERT AS "
3378+
+ "INSERT INTO " + testTableB + " DEFAULT VALUES");
3379+
3380+
// Insert row into TABLE_A requesting generated keys
3381+
String sql = "INSERT INTO " + testTableA + " (NAME) VALUES (?)";
3382+
try (PreparedStatement ps = conn.prepareStatement(sql, new String[]{"ID"})) {
3383+
ps.setString(1, "test");
3384+
3385+
// Execute the insert + trigger
3386+
ps.execute();
3387+
3388+
// Validate update count is correct (should be 1 for the INSERT)
3389+
int updateCount = ps.getUpdateCount();
3390+
assertEquals(1, updateCount, "Update count should be 1 for single INSERT");
3391+
3392+
// Validate generated keys can be retrieved (this was broken before the fix)
3393+
try (ResultSet rs = ps.getGeneratedKeys()) {
3394+
assertTrue(rs.next(), "Generated keys ResultSet should have at least one row");
3395+
int generatedKey = rs.getInt(1);
3396+
assertTrue(generatedKey > 0, "Generated key should be a positive integer, got: " + generatedKey);
3397+
}
3398+
}
3399+
3400+
// Cleanup
3401+
TestUtils.dropTriggerIfExists(testTrigger, stmt);
3402+
TestUtils.dropTableIfExists(testTableB, stmt);
3403+
TestUtils.dropTableIfExists(testTableA, stmt);
3404+
3405+
}
3406+
}
3407+
33273408
@AfterEach
33283409
public void terminate() {
33293410
try (Connection con = getConnection(); Statement stmt = con.createStatement()) {

0 commit comments

Comments
 (0)