Skip to content

Commit 65b2724

Browse files
authored
Merge pull request #2443 from jameshartig/x-invalidate-cache-in-exec
fix: invalidate statement/description cache in Exec
2 parents e214c23 + b4e8061 commit 65b2724

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

conn.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,18 @@ optionLoop:
515515
mode = QueryExecModeSimpleProtocol
516516
}
517517

518+
defer func() {
519+
if err != nil {
520+
if sc := c.statementCache; sc != nil {
521+
sc.Invalidate(sql)
522+
}
523+
524+
if sc := c.descriptionCache; sc != nil {
525+
sc.Invalidate(sql)
526+
}
527+
}
528+
}()
529+
518530
if sd, ok := c.preparedStatements[sql]; ok {
519531
return c.execPrepared(ctx, sd, arguments)
520532
}

conn_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,48 @@ func TestStmtCacheInvalidationTxWithBatch(t *testing.T) {
15291529
ensureConnValid(t, conn)
15301530
}
15311531

1532+
// https://github.com/jackc/pgx/issues/2442
1533+
func TestStmtCacheInvalidationExec(t *testing.T) {
1534+
ctx := context.Background()
1535+
1536+
conn := mustConnectString(t, os.Getenv("PGX_TEST_DATABASE"))
1537+
defer closeConn(t, conn)
1538+
1539+
// create a table and fill it with some data
1540+
_, err := conn.Exec(ctx, `
1541+
DROP TABLE IF EXISTS drop_cols;
1542+
CREATE TABLE drop_cols (
1543+
id SERIAL PRIMARY KEY NOT NULL,
1544+
f1 int NOT NULL,
1545+
f2 int NOT NULL
1546+
);
1547+
`)
1548+
require.NoError(t, err)
1549+
1550+
insertSQL := "INSERT INTO drop_cols (f1, f2) VALUES ($1, $2)"
1551+
// This query will populate the statement cache.
1552+
_, err = conn.Exec(ctx, insertSQL, 1, 2)
1553+
require.NoError(t, err)
1554+
1555+
// Now, change the schema of the table out from under the statement, making it invalid.
1556+
_, err = conn.Exec(ctx, "ALTER TABLE drop_cols ALTER COLUMN f1 TYPE boolean USING f1::boolean")
1557+
require.NoError(t, err)
1558+
1559+
// We must get an error the first time we try to re-execute a bad statement.
1560+
// It is up to the application to determine if it wants to try again. We punt to
1561+
// the application because there is no clear recovery path in the case of failed transactions
1562+
// or batch operations and because automatic retry is tricky and we don't want to get
1563+
// it wrong at such an importaint layer of the stack.
1564+
_, err = conn.Exec(ctx, insertSQL, true, 2)
1565+
require.ErrorContains(t, err, "failed to encode args[0]")
1566+
1567+
// On retry, the statement should have been flushed from the cache.
1568+
_, err = conn.Exec(ctx, insertSQL, true, 2)
1569+
require.NoError(t, err)
1570+
1571+
ensureConnValid(t, conn)
1572+
}
1573+
15321574
func TestInsertDurationInterval(t *testing.T) {
15331575
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
15341576
defer cancel()

0 commit comments

Comments
 (0)