diff --git a/pkg/ccl/logictestccl/testdata/logic_test/udf_plpgsql b/pkg/ccl/logictestccl/testdata/logic_test/udf_plpgsql index 91a97576e0f4..35ca8aaf891a 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/udf_plpgsql +++ b/pkg/ccl/logictestccl/testdata/logic_test/udf_plpgsql @@ -3409,3 +3409,42 @@ NOTICE: i: 4 j: 2 NOTICE: outer_counter: 4 inner_counter: 8 subtest end + +# Regression test for a failure to step the read sequence number after catching +# an exception and restoring a savepoint (#156412). +subtest regression_156412 + +statement ok +CREATE TABLE t122278 (pk INT PRIMARY KEY); + +statement ok +CREATE FUNCTION f(n INT) RETURNS INT AS $$ + BEGIN + BEGIN + IF n = 0 THEN + RETURN 1 // 0; + END IF; + EXCEPTION + WHEN division_by_zero THEN + RETURN (SELECT 100 + count(*) FROM t122278); + END; + RETURN 1 // 0; + EXCEPTION + WHEN division_by_zero THEN + RETURN (SELECT 200 + count(*) FROM t122278); + END +$$ LANGUAGE PLpgSQL; + +statement ok +BEGIN; + +statement ok +INSERT INTO t122278 values (1); + +statement ok +SELECT f(0); + +statement ok +COMMIT; + +subtest end diff --git a/pkg/kv/txn.go b/pkg/kv/txn.go index c1f6bd824293..8099f93b2c8e 100644 --- a/pkg/kv/txn.go +++ b/pkg/kv/txn.go @@ -1825,6 +1825,9 @@ func (txn *Txn) CreateSavepoint(ctx context.Context) (SavepointToken, error) { // and can be reused later (e.g. to release or roll back again). // // This method is only valid when called on RootTxns. +// +// NB: after calling RollbackToSavepoint, the transaction's read sequence number +// must be stepped by calling Step() before any further reads are performed. func (txn *Txn) RollbackToSavepoint(ctx context.Context, s SavepointToken) error { txn.mu.Lock() defer txn.mu.Unlock() diff --git a/pkg/sql/routine.go b/pkg/sql/routine.go index 97b2966c109b..effca937da8f 100644 --- a/pkg/sql/routine.go +++ b/pkg/sql/routine.go @@ -460,12 +460,19 @@ func (g *routineGenerator) handleException(ctx context.Context, err error) error g.reset(ctx, g.p, branch, args) // Configure stepping for volatile routines so that mutations made by the - // invoking statement are visible to the routine. + // invoking statement are visible to the routine. Make sure to also step + // before caching the read sequence number, since the read sequence number + // may be part of "ignored" list set when restoring the savepoint above. var prevSteppingMode kv.SteppingMode var prevSeqNum enginepb.TxnSeq txn := g.p.Txn() if g.expr.EnableStepping { prevSteppingMode = txn.ConfigureStepping(ctx, kv.SteppingEnabled) + stepErr := txn.Step(ctx, false /* allowReadTimestampStep */) + if stepErr != nil { + // This error is unexpected, so return immediately. + return errors.CombineErrors(err, errors.WithAssertionFailure(stepErr)) + } prevSeqNum = txn.GetReadSeqNum() }