@@ -469,7 +469,10 @@ EXPLAIN (COSTS OFF) SELECT * FROM test.range_rel_1 UNION ALL SELECT * FROM test.
469
469
* Join
470
470
*/
471
471
SET enable_hashjoin = OFF;
472
+ set enable_nestloop = OFF;
472
473
SET enable_mergejoin = ON;
474
+ SET pg_pathman.enable_runtimeappend = OFF;
475
+ SET pg_pathman.enable_runtimemergeappend = OFF;
473
476
EXPLAIN (COSTS OFF)
474
477
SELECT * FROM test.range_rel j1
475
478
JOIN test.range_rel j2 on j2.id = j1.id
@@ -560,6 +563,218 @@ SELECT * FROM ttt;
560
563
Filter: (value = 2)
561
564
(5 rows)
562
565
566
+ /*
567
+ * Test RuntimeAppend
568
+ */
569
+ create or replace function test.pathman_assert(smt bool, error_msg text) returns text as $$
570
+ begin
571
+ if not smt then
572
+ raise exception '%', error_msg;
573
+ end if;
574
+
575
+ return 'ok';
576
+ end;
577
+ $$ language plpgsql;
578
+ create or replace function test.pathman_equal(a text, b text, error_msg text) returns text as $$
579
+ begin
580
+ if a != b then
581
+ raise exception '''%'' is not equal to ''%'', %', a, b, error_msg;
582
+ end if;
583
+
584
+ return 'equal';
585
+ end;
586
+ $$ language plpgsql;
587
+ create or replace function test.pathman_test(query text) returns jsonb as $$
588
+ declare
589
+ plan jsonb;
590
+ begin
591
+ execute 'explain (analyze, format json)' || query into plan;
592
+
593
+ return plan;
594
+ end;
595
+ $$ language plpgsql;
596
+ create or replace function test.pathman_test_1() returns text as $$
597
+ declare
598
+ plan jsonb;
599
+ num int;
600
+ begin
601
+ plan = test.pathman_test('select * from test.runtime_test_1 where id = (select * from test.run_values limit 1)');
602
+
603
+ perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text,
604
+ '"Custom Scan"',
605
+ 'wrong plan type');
606
+
607
+ perform test.pathman_equal((plan->0->'Plan'->'Custom Plan Provider')::text,
608
+ '"RuntimeAppend"',
609
+ 'wrong plan provider');
610
+
611
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Relation Name')::text,
612
+ '"runtime_test_1_1"',
613
+ 'wrong partition');
614
+ return 'ok';
615
+ end;
616
+ $$ language plpgsql;
617
+ create or replace function test.pathman_test_2() returns text as $$
618
+ declare
619
+ plan jsonb;
620
+ num int;
621
+ begin
622
+ plan = test.pathman_test('select * from test.runtime_test_1 where id = any (select * from test.run_values limit 6)');
623
+
624
+ perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text,
625
+ '"Nested Loop"',
626
+ 'wrong plan type');
627
+
628
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Node Type')::text,
629
+ '"Custom Scan"',
630
+ 'wrong plan type');
631
+
632
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Custom Plan Provider')::text,
633
+ '"RuntimeAppend"',
634
+ 'wrong plan provider');
635
+
636
+ select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans'->1->'Plans') into num;
637
+ perform test.pathman_equal(num::text, '6', 'expected 6 child plans for custom scan');
638
+
639
+ for i in 0..5 loop
640
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Plans'->i->'Relation Name')::text,
641
+ format('"runtime_test_1_%s"', i + 1),
642
+ 'wrong partition');
643
+
644
+ num = plan->0->'Plan'->'Plans'->1->'Plans'->i->'Actual Loops';
645
+ perform test.pathman_equal(num::text, '1', 'expected 1 loop');
646
+ end loop;
647
+
648
+ return 'ok';
649
+ end;
650
+ $$ language plpgsql;
651
+ create or replace function test.pathman_test_3() returns text as $$
652
+ declare
653
+ plan jsonb;
654
+ num int;
655
+ begin
656
+ plan = test.pathman_test('select * from test.runtime_test_1 a join test.run_values b on a.id = b.val');
657
+
658
+ perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text,
659
+ '"Nested Loop"',
660
+ 'wrong plan type');
661
+
662
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Node Type')::text,
663
+ '"Custom Scan"',
664
+ 'wrong plan type');
665
+
666
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Custom Plan Provider')::text,
667
+ '"RuntimeAppend"',
668
+ 'wrong plan provider');
669
+
670
+ select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans'->1->'Plans') into num;
671
+ perform test.pathman_equal(num::text, '128', 'expected 128 child plans for custom scan');
672
+
673
+ for i in 0..127 loop
674
+ num = plan->0->'Plan'->'Plans'->1->'Plans'->i->'Actual Loops';
675
+ perform test.pathman_assert(num <= 79, 'expected no more than 79 loops');
676
+ end loop;
677
+
678
+ return 'ok';
679
+ end;
680
+ $$ language plpgsql;
681
+ create or replace function test.pathman_test_4() returns text as $$
682
+ declare
683
+ plan jsonb;
684
+ num int;
685
+ begin
686
+ plan = test.pathman_test('select * from test.category c, lateral' ||
687
+ '(select * from test.runtime_test_2 g where g.category_id = c.id order by rating limit 10) as tg');
688
+
689
+ perform test.pathman_equal((plan->0->'Plan'->'Node Type')::text,
690
+ '"Nested Loop"',
691
+ 'wrong plan type');
692
+
693
+ /* Limit -> Custom Scan */
694
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->0->'Node Type')::text,
695
+ '"Custom Scan"',
696
+ 'wrong plan type');
697
+
698
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->0->'Custom Plan Provider')::text,
699
+ '"RuntimeMergeAppend"',
700
+ 'wrong plan provider');
701
+
702
+ select count(*) from jsonb_array_elements_text(plan->0->'Plan'->'Plans'->1->'Plans'->0->'Plans') into num;
703
+ perform test.pathman_equal(num::text, '10', 'expected 10 child plans for custom scan');
704
+
705
+ for i in 0..9 loop
706
+ perform test.pathman_equal((plan->0->'Plan'->'Plans'->1->'Plans'->0->'Plans'->i->'Relation Name')::text,
707
+ format('"runtime_test_2_%s"', i + 1),
708
+ 'wrong partition');
709
+
710
+ num = plan->0->'Plan'->'Plans'->1->'Plans'->0->'Plans'->i->'Actual Loops';
711
+ perform test.pathman_assert(num = 1, 'expected no more than 1 loops');
712
+ end loop;
713
+
714
+ return 'ok';
715
+ end;
716
+ $$ language plpgsql;
717
+ create table test.run_values as select generate_series(1, 10000) val;
718
+ create table test.runtime_test_1(id serial primary key, val real);
719
+ insert into test.runtime_test_1 select generate_series(1, 10000), random();
720
+ select pathman.create_hash_partitions('test.runtime_test_1', 'id', 128);
721
+ NOTICE: function test.runtime_test_1_insert_trigger_func() does not exist, skipping
722
+ NOTICE: function test.runtime_test_1_update_trigger_func() does not exist, skipping
723
+ NOTICE: Copying data to partitions...
724
+ create_hash_partitions
725
+ ------------------------
726
+ 128
727
+ (1 row)
728
+
729
+ create table test.category as (select id, 'cat' || id::text as name from generate_series(1, 10) id);
730
+ create table test.runtime_test_2 (id serial, category_id int not null, name text, rating real);
731
+ insert into test.runtime_test_2 (select id, (id % 10) + 1 as category_id, 'good' || id::text as name, random() as rating from generate_series(1, 1000000) id);
732
+ create index on test.runtime_test_2 (category_id, rating);
733
+ select pathman.create_hash_partitions('test.runtime_test_2', 'category_id', 128);
734
+ NOTICE: function test.runtime_test_2_insert_trigger_func() does not exist, skipping
735
+ NOTICE: function test.runtime_test_2_update_trigger_func() does not exist, skipping
736
+ NOTICE: Copying data to partitions...
737
+ create_hash_partitions
738
+ ------------------------
739
+ 128
740
+ (1 row)
741
+
742
+ analyze test.run_values;
743
+ analyze test.runtime_test_1;
744
+ set enable_mergejoin = off;
745
+ set enable_hashjoin = off;
746
+ set pg_pathman.enable_runtimeappend = on;
747
+ set pg_pathman.enable_runtimemergeappend = on;
748
+ select test.pathman_test_1(); /* RuntimeAppend (select ... where id = (subquery)) */
749
+ pathman_test_1
750
+ ----------------
751
+ ok
752
+ (1 row)
753
+
754
+ select test.pathman_test_2(); /* RuntimeAppend (select ... where id = any(subquery)) */
755
+ pathman_test_2
756
+ ----------------
757
+ ok
758
+ (1 row)
759
+
760
+ select test.pathman_test_3(); /* RuntimeAppend (a join b on a.id = b.val) */
761
+ pathman_test_3
762
+ ----------------
763
+ ok
764
+ (1 row)
765
+
766
+ select test.pathman_test_4(); /* RuntimeMergeAppend (lateral) */
767
+ pathman_test_4
768
+ ----------------
769
+ ok
770
+ (1 row)
771
+
772
+ set pg_pathman.enable_runtimeappend = off;
773
+ set pg_pathman.enable_runtimemergeappend = off;
774
+ set enable_mergejoin = on;
775
+ set enable_hashjoin = on;
776
+ drop table test.run_values, test.runtime_test_1, test.runtime_test_2 cascade;
777
+ NOTICE: drop cascades to 256 other objects
563
778
/*
564
779
* Test split and merge
565
780
*/
@@ -632,6 +847,13 @@ NOTICE: Done!
632
847
test.num_range_rel_6
633
848
(1 row)
634
849
850
+ EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id >= 4000;
851
+ QUERY PLAN
852
+ -----------------------------------
853
+ Append
854
+ -> Seq Scan on num_range_rel_6
855
+ (2 rows)
856
+
635
857
SELECT pathman.prepend_range_partition('test.num_range_rel');
636
858
NOTICE: Prepending new partition...
637
859
NOTICE: Done!
@@ -640,6 +862,13 @@ NOTICE: Done!
640
862
test.num_range_rel_7
641
863
(1 row)
642
864
865
+ EXPLAIN (COSTS OFF) SELECT * FROM test.num_range_rel WHERE id < 0;
866
+ QUERY PLAN
867
+ -----------------------------------
868
+ Append
869
+ -> Seq Scan on num_range_rel_7
870
+ (2 rows)
871
+
643
872
SELECT pathman.drop_range_partition('test.num_range_rel_7');
644
873
drop_range_partition
645
874
----------------------
@@ -1071,7 +1300,7 @@ NOTICE: drop cascades to 5 other objects
1071
1300
SELECT * FROM pathman.pathman_config;
1072
1301
id | relname | attname | parttype | range_interval
1073
1302
----+--------------------+---------+----------+----------------
1074
- 6 | test.num_range_rel | id | 2 | 1000
1303
+ 8 | test.num_range_rel | id | 2 | 1000
1075
1304
(1 row)
1076
1305
1077
1306
CREATE TABLE test."RangeRel" (