@@ -410,3 +410,159 @@ SELECT current_schema() ~ 'pg_temp' AS is_temp_schema;
410
410
411
411
PREPARE TRANSACTION 'twophase_search';
412
412
ERROR: cannot PREPARE a transaction that has operated on temporary objects
413
+ -- Tests to verify we recover correctly from exhausting buffer pins and
414
+ -- related matters.
415
+ -- use lower possible buffer limit to make the test cheaper
416
+ \c
417
+ SET temp_buffers = 100;
418
+ CREATE TEMPORARY TABLE test_temp(a int not null unique, b TEXT not null, cnt int not null);
419
+ INSERT INTO test_temp SELECT generate_series(1, 10000) as id, repeat('a', 200), 0;
420
+ -- should be at least 2x as large than temp_buffers
421
+ SELECT pg_relation_size('test_temp') / current_setting('block_size')::int8 > 200;
422
+ ?column?
423
+ ----------
424
+ t
425
+ (1 row)
426
+
427
+ -- Don't want cursor names and plpgsql function lines in the error messages
428
+ \set VERBOSITY terse
429
+ /* helper function to create cursors for each page in [p_start, p_end] */
430
+ CREATE FUNCTION test_temp_pin(p_start int, p_end int)
431
+ RETURNS void
432
+ LANGUAGE plpgsql
433
+ AS $f$
434
+ DECLARE
435
+ cursorname text;
436
+ query text;
437
+ BEGIN
438
+ FOR i IN p_start..p_end LOOP
439
+ cursorname = 'c_'||i;
440
+ query = format($q$DECLARE %I CURSOR FOR SELECT ctid FROM test_temp WHERE ctid >= '( %s, 1)'::tid $q$, cursorname, i);
441
+ EXECUTE query;
442
+ EXECUTE 'FETCH NEXT FROM '||cursorname;
443
+ -- for test development
444
+ -- RAISE NOTICE '%: %', cursorname, query;
445
+ END LOOP;
446
+ END;
447
+ $f$;
448
+ -- Test overflow of temp table buffers is handled correctly
449
+ BEGIN;
450
+ -- should work, below max
451
+ SELECT test_temp_pin(0, 9);
452
+ test_temp_pin
453
+ ---------------
454
+
455
+ (1 row)
456
+
457
+ -- should fail, too many buffers pinned
458
+ SELECT test_temp_pin(10, 105);
459
+ ERROR: no empty local buffer available
460
+ ROLLBACK;
461
+ BEGIN;
462
+ -- have some working cursors to test after errors
463
+ SELECT test_temp_pin(0, 9);
464
+ test_temp_pin
465
+ ---------------
466
+
467
+ (1 row)
468
+
469
+ FETCH NEXT FROM c_3;
470
+ ctid
471
+ -------
472
+ (3,2)
473
+ (1 row)
474
+
475
+ -- exhaust buffer pins in subtrans, check things work after
476
+ SAVEPOINT rescue_me;
477
+ SELECT test_temp_pin(10, 105);
478
+ ERROR: no empty local buffer available
479
+ ROLLBACK TO SAVEPOINT rescue_me;
480
+ -- pre-subtrans cursors continue to work
481
+ FETCH NEXT FROM c_3;
482
+ ctid
483
+ -------
484
+ (3,3)
485
+ (1 row)
486
+
487
+ -- new cursors with pins can be created after subtrans rollback
488
+ SELECT test_temp_pin(10, 94);
489
+ test_temp_pin
490
+ ---------------
491
+
492
+ (1 row)
493
+
494
+ -- Check that read streams deal with lower number of pins available
495
+ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp;
496
+ count | max_a | min_a | max_cnt
497
+ -------+-------+-------+---------
498
+ 10000 | 10000 | 1 | 0
499
+ (1 row)
500
+
501
+ ROLLBACK;
502
+ -- Check that temp tables with existing cursors can't be dropped.
503
+ BEGIN;
504
+ SELECT test_temp_pin(0, 1);
505
+ test_temp_pin
506
+ ---------------
507
+
508
+ (1 row)
509
+
510
+ DROP TABLE test_temp;
511
+ ERROR: cannot DROP TABLE "test_temp" because it is being used by active queries in this session
512
+ COMMIT;
513
+ -- Check that temp tables with existing cursors can't be dropped.
514
+ BEGIN;
515
+ SELECT test_temp_pin(0, 1);
516
+ test_temp_pin
517
+ ---------------
518
+
519
+ (1 row)
520
+
521
+ TRUNCATE test_temp;
522
+ ERROR: cannot TRUNCATE "test_temp" because it is being used by active queries in this session
523
+ COMMIT;
524
+ -- Check that temp tables that are dropped in transaction that's rolled back
525
+ -- preserve buffer contents
526
+ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp;
527
+ count | max_a | min_a | max_cnt
528
+ -------+-------+-------+---------
529
+ 10000 | 10000 | 1 | 0
530
+ (1 row)
531
+
532
+ INSERT INTO test_temp(a, b, cnt) VALUES (-1, '', 0);
533
+ BEGIN;
534
+ INSERT INTO test_temp(a, b, cnt) VALUES (-2, '', 0);
535
+ DROP TABLE test_temp;
536
+ ROLLBACK;
537
+ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp;
538
+ count | max_a | min_a | max_cnt
539
+ -------+-------+-------+---------
540
+ 10001 | 10000 | -1 | 0
541
+ (1 row)
542
+
543
+ -- Check that temp table drop is transactional and preserves dirty
544
+ -- buffer contents
545
+ UPDATE test_temp SET cnt = cnt + 1 WHERE a = -1;
546
+ BEGIN;
547
+ DROP TABLE test_temp;
548
+ ROLLBACK;
549
+ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp;
550
+ count | max_a | min_a | max_cnt
551
+ -------+-------+-------+---------
552
+ 10001 | 10000 | -1 | 1
553
+ (1 row)
554
+
555
+ -- Check that temp table truncation is transactional and preserves dirty
556
+ -- buffer contents
557
+ UPDATE test_temp SET cnt = cnt + 1 WHERE a = -1;
558
+ BEGIN;
559
+ TRUNCATE test_temp;
560
+ ROLLBACK;
561
+ SELECT count(*), max(a) max_a, min(a) min_a, max(cnt) max_cnt FROM test_temp;
562
+ count | max_a | min_a | max_cnt
563
+ -------+-------+-------+---------
564
+ 10001 | 10000 | -1 | 2
565
+ (1 row)
566
+
567
+ -- cleanup
568
+ DROP FUNCTION test_temp_pin(int, int);
0 commit comments