@@ -538,3 +538,77 @@ func tcTableNotFound(
538538 _ , err = session .Prepare (ctx , "table-not-found-select" , stmt , nil )
539539 require .ErrorContains (t , err , "relation \" defaultdb.non_existent_table\" does not exist" )
540540}
541+
542+ func TestSavepoint (t * testing.T ) {
543+ defer leaktest .AfterTest (t )()
544+ defer log .Scope (t ).Close (t )
545+
546+ ctx := context .Background ()
547+ s := serverutils .StartServerOnly (t , base.TestServerArgs {})
548+ defer s .Stopper ().Stop (ctx )
549+
550+ db := s .SQLConn (t )
551+ _ , err := db .Exec ("CREATE TABLE test (id INT PRIMARY KEY, val INT)" )
552+ require .NoError (t , err )
553+
554+ idb := s .InternalDB ().(descs.DB )
555+ session , err := idb .Session (ctx , "test-session" )
556+ require .NoError (t , err )
557+ defer session .Close (ctx )
558+
559+ insertStmt , err := parser .ParseOne ("INSERT INTO defaultdb.test VALUES ($1, $2)" )
560+ require .NoError (t , err )
561+
562+ insertPrepared , err := session .Prepare (ctx , "insert" , insertStmt , []* types.T {types .Int , types .Int })
563+ require .NoError (t , err )
564+
565+ selectStmt , err := parser .ParseOne ("SELECT id, val FROM defaultdb.test ORDER BY id" )
566+ require .NoError (t , err )
567+
568+ selectPrepared , err := session .Prepare (ctx , "select" , selectStmt , nil )
569+ require .NoError (t , err )
570+
571+ // Create a a three deep savepoint and rollback the inner two savepoints.
572+ err = session .Txn (ctx , func (ctx context.Context ) error {
573+ return session .Savepoint (ctx , func (ctx context.Context ) error {
574+ _ , err := session .ExecutePrepared (ctx , insertPrepared , tree.Datums {
575+ tree .NewDInt (tree .DInt (1 )),
576+ tree .NewDInt (tree .DInt (10 )),
577+ })
578+ if err != nil {
579+ return err
580+ }
581+ err = session .Savepoint (ctx , func (ctx context.Context ) error {
582+ _ , err := session .ExecutePrepared (ctx , insertPrepared , tree.Datums {
583+ tree .NewDInt (tree .DInt (2 )),
584+ tree .NewDInt (tree .DInt (20 )),
585+ })
586+ if err != nil {
587+ return err
588+ }
589+ return session .Savepoint (ctx , func (ctx context.Context ) error {
590+ _ , err := session .ExecutePrepared (ctx , insertPrepared , tree.Datums {
591+ tree .NewDInt (tree .DInt (3 )),
592+ tree .NewDInt (tree .DInt (30 )),
593+ })
594+ if err != nil {
595+ return err
596+ }
597+ // Rollback this innermost savepoint
598+ return errors .New ("rollback innermost savepoint" )
599+ })
600+ })
601+ // The second savepoint should fail due to the nested savepoint rollback
602+ require .ErrorContains (t , err , "rollback innermost savepoint" )
603+ return nil
604+ })
605+ })
606+ require .NoError (t , err )
607+
608+ // Verify final state - only first insert should be committed
609+ rows , err := session .QueryPrepared (ctx , selectPrepared , nil )
610+ require .NoError (t , err )
611+ require .Equal (t , []tree.Datums {
612+ {tree .NewDInt (tree .DInt (1 )), tree .NewDInt (tree .DInt (10 ))},
613+ }, rows )
614+ }
0 commit comments