forked from postgres/postgres
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dynamic_saop_advancement.sql
4502 lines (4005 loc) · 194 KB
/
dynamic_saop_advancement.sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
set work_mem='100MB';
set effective_io_concurrency=100;
set effective_cache_size='24GB';
set maintenance_io_concurrency=100;
set random_page_cost=2.0;
set track_io_timing to off;
set enable_seqscan to off;
set client_min_messages=error;
set vacuum_freeze_min_age = 0;
create extension if not exists pageinspect; -- just to have it
reset client_min_messages;
-- Set log_btree_verbosity to 1 without depending on having that patch
-- applied (HACK, just sets commit_siblings instead when we don't have that
-- patch available):
select set_config((select coalesce((select name from pg_settings where name = 'log_btree_verbosity'), 'commit_siblings')), '1', false);
-- Establish if this server is master or the patch -- want to skip stress
-- tests if it's the latter
--
-- Reminder: Don't vary the database state between master and patch (just the
-- tests run, which must be read-only)
select (setting = '5432') as testing_patch from pg_settings where name = 'port'
\gset
-------------------------------
-- Basic single column tests --
-------------------------------
set client_min_messages=error;
drop table if exists skippy_tbl;
reset client_min_messages;
create unlogged table skippy_tbl(
bar int4
);
create index skippy_idx on skippy_tbl(bar);
insert into skippy_tbl
select
i
from
generate_series(1, 500) i;
-- prewarm
select count(*) from skippy_tbl;
vacuum analyze skippy_tbl;
-------------------------------
-- Index scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
-- Simple example:
select ctid, bar from skippy_tbl where bar in (2,3,4);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,3,4);
-- Simple example of a backwards scan:
select ctid, bar from skippy_tbl where bar in (2,3,4) order by bar desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,3,4) order by bar desc;
-- continuescan-on-highkey case should work:
select ctid, bar from skippy_tbl where bar in (365,366);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (365,366);
-- pivotsearch (first item on leftmost leaf page's right sibling page) case
-- should also work:
select ctid, bar from skippy_tbl where bar in (367,368);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (367,368);
-- Gap of one shouldn't confuse us:
select ctid, bar from skippy_tbl where bar in (2,4);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,4);
-- Backwards scan gap of one shouldn't confuse us:
select ctid, bar from skippy_tbl where bar in (2,4) order by bar desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,4) order by bar desc;
-- Gap of two shouldn't confuse us:
select ctid, bar from skippy_tbl where bar in (2,5);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,5);
-- Backwards scan gap of two shouldn't confuse us:
select ctid, bar from skippy_tbl where bar in (2,5) order by bar desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,5) order by bar desc;
-- Adjoining non-pivot tuples split only by leaf page high key should require
-- only one descent of btree, so second page is read by read next page path:
select ctid, bar from skippy_tbl where bar in (366,367);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (366,367);
-- Equivalent backwards scan:
select ctid, bar from skippy_tbl where bar in (366,367) order by bar desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (366,367) order by bar desc;
-- Index-only scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to on;
set enable_indexscan to off;
-- This is the one that sometimes uses a sequential scan (when run as part of
-- the whole pg_regress suite):
select bar from skippy_tbl where bar in (2,3,4);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select bar from skippy_tbl where bar in (2,3,4);
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
select ctid, bar from skippy_tbl where bar in (2,3,4);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (2,3,4);
-- Same as "simple example", but with duplicates:
insert into skippy_tbl(bar) values (22), (23), (23), (24), (24), (24);
vacuum analyze skippy_tbl;
select ctid, bar from skippy_tbl where bar in (22,23,24) order by bar;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (22,23,24) order by bar;
-- 3 non-pivot tuple matches:
select * from skippy_tbl where bar in (362,365,366);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from skippy_tbl where bar in (362,365,366);
-- Test non-SAOP case in passing, to avoid regressions in how we handle
-- more standard "boundary cases":
select * from skippy_tbl where bar = 366;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from skippy_tbl where bar = 366;
select * from skippy_tbl where bar = 367;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from skippy_tbl where bar = 367;
---------------------------------------------------
-- Large group of duplicates spanning many pages --
---------------------------------------------------
insert into skippy_tbl
select
555
from
generate_series(1, 3000) i;
vacuum analyze skippy_tbl;
-- Looks like this now:
--
-- ┌───┬───────┬───────┬────────┬────────┬────────────┬───────┬───────┬───────────────────┬─────────┬───────────┬──────────────────────────────────┐
-- │ i │ blkno │ flags │ nhtids │ nhblks │ ndeadhblks │ nlive │ ndead │ nhtidschecksimple │ avgsize │ freespace │ highkey │
-- ├───┼───────┼───────┼────────┼────────┼────────────┼───────┼───────┼───────────────────┼─────────┼───────────┼──────────────────────────────────┤
-- │ 1 │ 1 │ 1 │ 372 │ 3 │ 0 │ 373 │ 0 │ 0 │ 16 │ 688 │ (bar)=(367) │
-- │ 2 │ 2 │ 1 │ 134 │ 2 │ 0 │ 135 │ 0 │ 0 │ 16 │ 5,448 │ (bar)=(555) │
-- │ 3 │ 4 │ 1 │ 1,278 │ 6 │ 0 │ 7 │ 0 │ 0 │ 1,115 │ 312 │ (bar)=(555), (htid)=('(7,202)') │
-- │ 4 │ 5 │ 1 │ 1,278 │ 7 │ 0 │ 7 │ 0 │ 0 │ 1,115 │ 312 │ (bar)=(555), (htid)=('(13,124)') │
-- │ 5 │ 6 │ 1 │ 444 │ 3 │ 0 │ 39 │ 0 │ 0 │ 78 │ 4,920 │ ∅ │
-- └───┴───────┴───────┴────────┴────────┴────────────┴───────┴───────┴───────────────────┴─────────┴───────────┴──────────────────────────────────┘
--
---------------------------------------------------
-- Scan blknos 2,4,5,6
--
-- This avoids continuescan termination on block 2, which used to happen due
-- to using the wrong scan key (the first, from 500 constant).
-- It's fixed, so now we switch to next SAOP element rather than
-- terminate _bt_first-wise/_bt_search-wise scan at that point
--
-- This does one less buffer access than master (only 5, not 6). Master has
-- an extra root page access, which we can avoid. It's only one less because
-- master does at least avoid visiting the same leaf page a second time in its
-- second _bt_first-wise scan of the index.
select count(*) from skippy_tbl where bar in (500,555);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select count(*) from skippy_tbl where bar in (500,555);
-- Same again, almost -- just don't scan blkno 2 this time
--
-- This results in one useful _bt_search call. We can avoid another useless
-- one by realizing that we already ran out of tuples to output at the end of
-- the fist _bt_search (which doesn't return any 556 rows either, since there
-- is nothing to return).
--
-- This variant of the query requires only 4 buffer accesses. As against 6
-- buffer accesses total in index for master branch. Here we win by more
-- compared to last time (by 2 buffer accesses) because the master branch
-- wasn't so lucky about not having to visit the same leaf page a second time.
select count(*) from skippy_tbl where bar in (555,556);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select count(*) from skippy_tbl where bar in (555,556);
-- The previous test case exercises how _bt_readpage deals with non-matches
-- covering key space > the highest non-pivot tuple in the index and < +inf.
-- Make sure that it continues to do that by checking the maximum value in the
-- index:
select max(bar) from skippy_tbl having max(bar) = 555; -- avoids regressing test coverage (i.e. tests the tests)
-- We do want to go through the root (3) to descend to the leftmost page (1) and then step to its right
-- sibling page (2):
-- XXX right now we don't do that -- what we actually do is redescend from the
-- root anew instead, just like the master branch -- so it's 4 buffer accesses
-- on the index instead of 3 accesses (we fall short of the obtainable
-- ideal, for now, since we're not yet able to be clever about using info from
-- internal pages -- nor are we willing to gamble even more aggressively).
select ctid, bar from skippy_tbl where bar in (1, 500);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, bar from skippy_tbl where bar in (1, 500);
-- Now show a similar case where even now we manage to get the obtainable
-- ideal (same path through the index is actually attained this time around).
-- This is possible here, independent of any speculative behavior and/or
-- cleverness when we descend the tree -- since the high key is 367, which
-- matches qual exactly. (We get only one descent and 3 buffer accesses,
-- versus master's 2 descents and 4 buffer accesses. We manage to do better
-- than master, despite the fact that even master doesn't revisit the same
-- leaf page twice here -- master's only failing is that it touches the root
-- page a second time.)
select ctid, * from skippy_tbl where bar = any ('{365,367}');
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, * from skippy_tbl where bar = any ('{365,367}');
-- However, the patch isn't entirely free of such speculative behavior.
-- Here is a more complicated case that manages to be optimal -- though
-- barely. Here we take a small gamble, and win.
--
-- This time around we don't have an exact leftmost page high key (367) match.
-- But we still win, since we do have 366 in both qual and in index (must be
-- both):
--
-- XXX UPDATE (December 3): Not anymore. No longer speculatively visit next
-- page without an exact match for non-truncated columns
select * from skippy_tbl where bar = any ('{365,366,368}');
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from skippy_tbl where bar = any ('{365,366,368}');
-- We were "between the values 366 and 368" at the point that we reached the
-- high key, whose value is 367 -- which is also "between" the same two
-- values. On that basis alone we decided to move right. We gambled and won.
-- This was a limited form of gamble that was only chosen because the only
-- value we were missing from page was the high key, 367. The high key is a
-- little special here.
--
-- Now lets try almost the same case, just with 366 missing. That has a
-- surprisingly big impact: now we won't gamble at all. This time when we
-- compare our search-type scan key to the non-pivot 366, we didn't get a
-- match, AND we terminated the scan locally (we accepted continuescan=false).
select * from skippy_tbl where bar = any ('{365,368}'); -- omit non-pivot value '366' this time
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from skippy_tbl where bar = any ('{365,368}'); -- 4 buffer accesses again
-- Now we'll show a gamble that doesn't pay off -- same rationale as earlier
-- gamble case, but this time we're not so lucky. As a result, this query
-- needs an extra buffer access compared to master/no optimization case:
--
-- (This time we lose because 2147483647 isn't on the next page, despite it
-- seeming like it might. XXX For now we'll accept this as a bad speculation;
-- a cost of doing business. Might want to rereview that decision later on.)
--
-- Here we get 5 index buffer hits (one extra):
--
-- XXX UPDATE (December 3): not anymore. As already noted in last UPDATE from
-- today, we don't move to next page when high key isn't an exact match in
-- respect of non-truncated attributes. So no extra buffer hit (4 hits only).
select * from skippy_tbl where bar = any ('{366,2147483647}');
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from skippy_tbl where bar = any ('{366,2147483647}');
-- (August 21) Infinite loop binary-search-array-keys bug test case:
insert into skippy_tbl select 2^31-1;
with a as (
select
i
from
generate_series(1, 150000) i
)
select count(*) from skippy_tbl
where bar = any(array[(select array_agg(i) from a)]);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
with a as (
select
i
from
generate_series(1, 150000) i
)
select count(*) from skippy_tbl
where bar = any(array[(select array_agg(i) from a)]);
-- Backwards scan (more or less equivalent)
set enable_sort = off;
with a as (
select
i
from
generate_series(1, 150000) i
)
select * from skippy_tbl
where bar = any(array[(select array_agg(i) from a)]) order by bar desc limit 50 offset 3000;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
with a as (
select
i
from
generate_series(1, 150000) i
)
select * from skippy_tbl
where bar = any(array[(select array_agg(i) from a)]) order by bar desc limit 50 offset 3000;
-----------------------------------------------------------------------
-- "More than one so->numArrayKeys" test case (uses 2 SAOPs/columns) --
-----------------------------------------------------------------------
set client_min_messages=error;
drop table if exists multi_test;
reset client_min_messages;
create unlogged table multi_test(
a int,
b int
);
create index multi_test_idx on multi_test(a, b);
insert into multi_test
select
j,
case when i < 14 then
0
else
1
end
from
generate_series(1, 14) i,
generate_series(1, 400) j
order by
j,
i;
vacuum analyze multi_test;
-- Looks like this now:
--
-- ┌───┬───────┬───────┬────────┬────────┬────────────┬───────┬───────┬───────────────────┬─────────┬───────────┬──────────────┐
-- │ i │ blkno │ flags │ nhtids │ nhblks │ ndeadhblks │ nlive │ ndead │ nhtidschecksimple │ avgsize │ freespace │ highkey │
-- ├───┼───────┼───────┼────────┼────────┼────────────┼───────┼───────┼───────────────────┼─────────┼───────────┼──────────────┤
-- │ 1 │ 1 │ 1 │ 854 │ 4 │ 0 │ 123 │ 0 │ 0 │ 55 │ 808 │ (a, b)=(62) │
-- │ 2 │ 2 │ 1 │ 854 │ 5 │ 0 │ 123 │ 0 │ 0 │ 55 │ 808 │ (a, b)=(123) │
-- │ 3 │ 4 │ 1 │ 854 │ 5 │ 0 │ 123 │ 0 │ 0 │ 55 │ 808 │ (a, b)=(184) │
-- │ 4 │ 5 │ 1 │ 854 │ 5 │ 0 │ 123 │ 0 │ 0 │ 55 │ 808 │ (a, b)=(245) │
-- │ 5 │ 6 │ 1 │ 854 │ 4 │ 0 │ 123 │ 0 │ 0 │ 55 │ 808 │ (a, b)=(306) │
-- │ 6 │ 7 │ 1 │ 854 │ 5 │ 0 │ 123 │ 0 │ 0 │ 55 │ 808 │ (a, b)=(367) │
-- │ 7 │ 8 │ 1 │ 476 │ 3 │ 0 │ 80 │ 0 │ 0 │ 49 │ 3,908 │ ∅ │
-- └───┴───────┴───────┴────────┴────────┴────────────┴───────┴───────┴───────────────────┴─────────┴───────────┴──────────────┘
--
-----------------------------------------------------------------------
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
-- Simpler case
-- Should only need to scan root page (3) plus a single leaf page (4)
--
-- This means that _bt_checkkeys() continuescan handling mustn't get confused
-- about boundary conditions in the presence of relatively complicated cases,
-- which this is -- multiple so->numArrayKeys is fairly rare.
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12);
-- Harder case
-- Should only need to scan root page (3) plus a single leaf page (4). This
-- is a bit trickier for _bt_checkkeys()-adjacent logic.
select * from multi_test where a in (123, 182, 183) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (123, 182, 183) and b in (1,2);
-- Hard case
-- Also needs to scan root page (3) plus leaf page 4 (like "Simpler case").
-- But this time we can't avoid going to a second leaf page -- leaf page 5.
-- That's where matches exceeding (184, -inf) are located.
select * from multi_test where a in (182, 183, 184) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 184) and b in (1,2);
-- Hard luck case
-- Here we gamble and lose. Almost like earlier skippy_tbl test case, but with
-- multiple SAOP columns for additional test coverage. And, we only get a
-- single _bt_search because we were "almost correct".
--
-- That is, we descend from the root (3) to leaf page 4, which has matches.
-- Then we gamble by moving right on the leaf level, moving to sibling page 5,
-- which has no matches. However, page 5 _does_ have a high key that makes us
-- want to move right again, to page 6 -- which is where our final match is
-- found!
select * from multi_test where a in (182, 183, 245) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 245) and b in (1,2); -- 4 buffer hits
-- Harder luck case
-- Two _bt_search descents this time (we _bt_first once we
-- reach page 5 because its high key indicates that it's time to quit gambling)
--
-- XXX UPDATE (December 3) Not anymore, no more moving to right page when our
-- high key lacks an exact match for non-truncated columns.
select * from multi_test where a in (182, 183, 306) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 306) and b in (1,2); -- 4 buffer hits
-- Not-SK_BT_REQFWD-but-still-insertion-scankey case
--
-- This is an example of how insertion scankey can have an attribute/value for
-- "b", even though "b" entry in search-type scankey doesn't end up SK_BT_REQFWD:
-- (_bt_array_continuescan actually encounters this directly, too)
select * from multi_test where a in (3,4,5) and b > 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b > 0;
-- Variant (for good luck)
select * from multi_test where a in (3,4,5) and b >= 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b >= 0;
-- This time we make "a" touch a boundary, in the style of "harder case":
select * from multi_test where a in (123, 182, 183) and b > 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (123, 182, 183) and b > 0; -- 2 buffer hits
-- This time we make "a" touch a boundary "inside the high key":
select * from multi_test where a in (123, 182, 183, 184) and b > 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (123, 182, 183, 184) and b > 0; -- 3 buffer hits
-- This time we make "b" search-type scankey required:
--
-- This is an example of the opposite: where an insertion scan key lacks an
-- entry corresponding to a search-type scankey's SK_BT_REQFWD entry.
-- (_bt_array_continuescan actually encounters this directly, too)
select * from multi_test where a in (3,4,5) and b < 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 0;
-- Variant (for good luck)
select * from multi_test where a in (3,4,5) and b < 1;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 1;
-- This time we make "a" touch a boundary, in the style of "harder case":
select * from multi_test where a in (123, 182, 183) and b < 3;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (123, 182, 183) and b < 3; -- 2 buffer hits
-- This time we make "a" touch a boundary "inside the high key":
select * from multi_test where a in (123, 182, 183, 184) and b < 3;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (123, 182, 183, 184) and b < 3; -- 3 buffer hits
-- Index scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
-- Simpler case
-- As above.
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12);
-- Now as a backwards scan
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12)
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12)
order by a desc, b desc;
-- Hard case
-- As above.
select * from multi_test where a in (182, 183, 184) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 184) and b in (1,2);
-- Hard case backwards scan variant (just for coverage):
set enable_sort=off;
select * from multi_test where a in (182, 183, 184) and b in (1,2) order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 184) and b in (1,2) order by a desc, b desc;
set enable_sort=on;
-- Hard luck case
-- As above.
select * from multi_test where a in (182, 183, 245) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 245) and b in (1,2);
-- Harder luck case
-- As above.
select * from multi_test where a in (182, 183, 306) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 306) and b in (1,2);
-- Not-SK_BT_REQFWD-but-still-insertion-scankey case
-- As above.
select * from multi_test where a in (3,4,5) and b > 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b > 0;
-- As a backwards scan:
select * from multi_test where a in (3,4,5) and b > 0
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b > 0
order by a desc, b desc;
-- Variant (for good luck)
select * from multi_test where a in (3,4,5) and b >= 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b >= 0;
-- As a backwards scan:
select * from multi_test where a in (3,4,5) and b >= 0
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b >= 0
order by a desc, b desc;
-- This time we make "b" search-type scankey required:
select * from multi_test where a in (3,4,5) and b < 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 0;
-- With <= instead of <:
select * from multi_test where a in (3,4,5) and b <= -1;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b <= -1;
-- As a backwards scan:
select * from multi_test where a in (3,4,5) and b < 0
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 0
order by a desc, b desc;
-- As a backwards scan with <= instead of <:
select * from multi_test where a in (3,4,5) and b <= -1
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b <= -1
order by a desc, b desc;
-- Variant (for good luck)
select * from multi_test where a in (3,4,5) and b < 1;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 1;
-- Variant (for good luck) with <= instead of <
select * from multi_test where a in (3,4,5) and b <= 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b <= 0;
-- As a backwards scan:
select * from multi_test where a in (3,4,5) and b < 1
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 1
order by a desc, b desc;
-- As a backwards scan with <= instead of <:
select * from multi_test where a in (3,4,5) and b <= 0
order by a desc, b desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b <= 0
order by a desc, b desc;
-- Index-only scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to on;
set enable_indexscan to off;
-- Simpler case
-- As above.
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (183) and b in (1,2,3,4,5,6,7,8,9,10,11,12);
-- Hard case
-- As above.
select * from multi_test where a in (182, 183, 184) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 184) and b in (1,2);
-- Hard luck case
-- As above.
select * from multi_test where a in (182, 183, 245) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 245) and b in (1,2);
-- Harder luck case
-- As above.
select * from multi_test where a in (182, 183, 306) and b in (1,2);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (182, 183, 306) and b in (1,2);
-- Not-SK_BT_REQFWD-but-still-insertion-scankey case
-- As above.
select * from multi_test where a in (3,4,5) and b > 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b > 0;
-- Variant (for good luck)
select * from multi_test where a in (3,4,5) and b >= 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b >= 0;
-- This time we make "b" search-type scankey required:
select * from multi_test where a in (3,4,5) and b < 0;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 0;
-- Variant (for good luck)
select * from multi_test where a in (3,4,5) and b < 1;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (3,4,5) and b < 1;
-- (November 5) when _bt_preprocess_array_keys has two arrays against the same
-- column that have no intersecting elements, preprocessing will leave the
-- arrays empty.
--
-- This test makes sure that both scan keys are eliminated (not just the
-- second).
select * from multi_test where a in (180,345) and a in (230, 300);
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select * from multi_test where a in (180,345) and a in (230, 300);
-------------------------------------------------------------------------------
-- tenk1 test cases involving queries where the optimization is inapplicable --
-------------------------------------------------------------------------------
set client_min_messages=error;
drop table if exists tenk1_dyn_saop;
reset client_min_messages;
\getenv abs_srcdir PG_ABS_SRCDIR
CREATE UNLOGGED TABLE tenk1_dyn_saop (
unique1 int4,
unique2 int4,
two int4,
four int4,
ten int4,
twenty int4,
hundred int4,
thousand int4,
twothousand int4,
fivethous int4,
tenthous int4,
odd int4,
even int4,
stringu1 name,
stringu2 name,
string4 name
);
ALTER TABLE tenk1_dyn_saop SET (autovacuum_enabled=off);
\set filename :abs_srcdir '/data/tenk.data'
COPY tenk1_dyn_saop FROM :'filename';
CREATE INDEX tenk1_dyn_saop_thous_tenthous ON tenk1_dyn_saop (thousand, tenthous);
VACUUM ANALYZE tenk1_dyn_saop;
-------------------------------------------------------------------------------
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
prepare regress_tenk1_inequality as
SELECT thousand, tenthous FROM tenk1_dyn_saop
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
execute regress_tenk1_inequality;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute regress_tenk1_inequality;
deallocate regress_tenk1_inequality;
-- Index scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
prepare regress_tenk1_inequality as
SELECT thousand, tenthous FROM tenk1_dyn_saop
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
execute regress_tenk1_inequality;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute regress_tenk1_inequality;
deallocate regress_tenk1_inequality;
-- Index-only scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to on;
set enable_indexscan to off;
prepare regress_tenk1_inequality as
SELECT thousand, tenthous FROM tenk1_dyn_saop
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
execute regress_tenk1_inequality;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute regress_tenk1_inequality;
deallocate regress_tenk1_inequality;
-- Now my own backwards scan variant, index-only scan:
prepare regress_tenk1_inequality_backwards as
SELECT thousand, tenthous FROM tenk1_dyn_saop
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand desc, tenthous desc;
execute regress_tenk1_inequality_backwards;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute regress_tenk1_inequality_backwards;
deallocate regress_tenk1_inequality_backwards;
------------------------------------------------------
-- Confusion about wraparound for high order column --
------------------------------------------------------
-- (September 13) This test case demonstrates the need to wraparound the
-- most significant column (which is "thousand" here) so that the scan will
-- terminate on the leftmost leaf page, without needlessly accessing further
-- leaf pages to the right.
--
-- This is surprisingly subtle, and seems like it's the only test case that'll
-- catch this. Note that what I describe is independent of the issue covered
-- by the next test case (nonarray_equality_strategy_orderproc_required_both_stages)
-- which was all about not doing the required comparisons in both functions.
-- This is about __not__ resetting cur_elem to zero for the "thousand" array once
-- the scan gets past the last "thousand = 1" tuple.
--
-- (October 28) We want to not only get the expected number of buffer hits; we
-- also want to terminate the scan within even incrementally advancing the
-- array keys. More concretely, it should look like this (and does, at the
-- time of writing):
--
-- (November 10): See also, must_wraparound_high_order_column_equality_nomatch
--
-- _bt_advance_array_keys, tuple: (thousand, tenthous)=(2, 2), 0x7ff88b087ea0 <-- first (2, *) tuple
-- numberOfKeys: 2
-- - sk_attno: 1, cur_elem 1/1, val: 1 [NULLS LAST, ASC]
-- - sk_attno: 2, cur_elem 9001/20500, val: 9001 [NULLS LAST, ASC]
-- + sk_attno: 1, cur_elem 1/1, val: 1 [NULLS LAST, ASC] <--- No changes here
-- + sk_attno: 2, cur_elem 9001/20500, val: 9001 [NULLS LAST, ASC] <--- Nor here
-- _bt_advance_array_keys: returns false
-- _bt_readpage final: (thousand, tenthous)=(2, 2), 0x7ff88b087ea0, from non-pivot offnum 22 TID (93,20) ended page and scan
-- _bt_readpage stats: currPos.firstItem: 0, currPos.lastItem: 19, nmatching: 20 ✅
-- _bt_first: returning offnum 2 TID (344,23)
-- _bt_readnextpage: ScanDirectionIsForward() case ran out of pages to the right
-- _bt_readnextpage: BTScanPosInvalidate() called for currPos
-- _bt_steppage: _bt_readnextpage() returns false so we do too
-- btendscan
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
prepare must_wraparound_high_order_column as
with a as (
select i from generate_series(0, 10500) i
)
select thousand, tenthous
from
tenk1_dyn_saop
where thousand in (0, 1) and
tenthous = any (array[(select array_agg(i) from a)]);
execute must_wraparound_high_order_column;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute must_wraparound_high_order_column;
deallocate must_wraparound_high_order_column;
-- Same again, but backwards scan for good luck
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
prepare must_wraparound_high_order_column_desc as
with a as (
select i from generate_series(0, 10500) i
)
select thousand, tenthous
from
tenk1_dyn_saop
where thousand in (0, 1) and
tenthous = any (array[(select array_agg(i) from a)])
order by thousand desc, tenthous desc;
execute must_wraparound_high_order_column_desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute must_wraparound_high_order_column_desc;
deallocate must_wraparound_high_order_column_desc;
-- (September 12) This test case decisively proves that we need to use
-- non-array required BTEqualStrategyNumber scan keys, both in the
-- precheck-current-keys function, and the function that actually advances the
-- array keys using tuple values.
--
-- For a while the test would fail (we'd do useless extra leaf page visits)
-- because I lacked the required infrastructure in at least one of these two
-- functions. This had surprisingly little (no?) coverage before then. This
-- test case makes it really obvious.
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
prepare nonarray_equality_strategy_orderproc_required_both_stages as
with a as (
select i from generate_series(-1, 10000) i
)
select
thousand,
tenthous
from
tenk1_dyn_saop
where thousand = 1 and tenthous = any (array[(select array_agg(i) from a)]);
execute nonarray_equality_strategy_orderproc_required_both_stages;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute nonarray_equality_strategy_orderproc_required_both_stages;
deallocate nonarray_equality_strategy_orderproc_required_both_stages;
-- Same again, but this time use a plain index scan for good luck:
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
prepare nonarray_equality_strategy_orderproc_required_both_stages as
with a as (
select i from generate_series(-1, 10000) i
)
select
thousand,
tenthous
from
tenk1_dyn_saop
where thousand = 1 and tenthous = any (array[(select array_agg(i) from a)]);
execute nonarray_equality_strategy_orderproc_required_both_stages;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute nonarray_equality_strategy_orderproc_required_both_stages;
deallocate nonarray_equality_strategy_orderproc_required_both_stages;
-- Same again, but this time use a backwards scan for good luck:
prepare nonarray_equality_strategy_orderproc_required_both_stages_desc as
with a as (
select i from generate_series(-1, 10000) i
)
select
thousand,
tenthous
from
tenk1_dyn_saop
where thousand = 1 and tenthous = any (array[(select array_agg(i) from a)])
order by thousand desc, tenthous desc;
execute nonarray_equality_strategy_orderproc_required_both_stages_desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute nonarray_equality_strategy_orderproc_required_both_stages_desc;
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
-- (September 19) But what about inequalities?
-- This is almost the same query as the last one, except we use < as a
-- replacement for =. We should get the same number of buffer hits.
prepare nonarray_inequality as
with a as (
select i from generate_series(-1, 10000) i
)
select
thousand,
tenthous
from
tenk1_dyn_saop
where thousand < 2 and tenthous = any (array[(select array_agg(i) from a)]);
execute nonarray_inequality;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF) -- 2 buffer hits, just like original = query
execute nonarray_inequality;
deallocate nonarray_inequality;
-- Same again, but this time a backwards scan for good luck:
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
prepare nonarray_inequality_desc as
with a as (
select i from generate_series(-1, 10000) i
)
select
thousand,
tenthous
from
tenk1_dyn_saop
where thousand < 2 and tenthous = any (array[(select array_agg(i) from a)])
order by thousand desc, tenthous desc;
execute nonarray_inequality_desc;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF) -- 2 buffer hits, just like original = query
execute nonarray_inequality_desc;
deallocate nonarray_inequality_desc;
-- Bitmap index scan:
set enable_bitmapscan to on;
set enable_indexonlyscan to off;
set enable_indexscan to off;
-- (November 10) This is like must_wraparound_high_order_column, except that
-- it has a new stressor for the implementation: the tenthous values the we'll
-- attempt to match are non-matches -- they're all too high to get any
-- matches.
prepare must_wraparound_high_order_column_equality_nomatch as
with a as (
select i from generate_series(10000, 10500) i
)
select
thousand,
tenthous
from tenk1_dyn_saop
where thousand in (0, 1) and tenthous = any (array[(select array_agg(i) from a)]);
execute must_wraparound_high_order_column_equality_nomatch;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute must_wraparound_high_order_column_equality_nomatch;
deallocate must_wraparound_high_order_column_equality_nomatch;
-- (November 10) This is like
-- must_wraparound_high_order_column_equality_nomatch, except that it uses an
-- inequality that's "equivalent" to its first array -- which renders the
-- second array on tenthous non-required. We expect identical access patterns
-- for both test cases (at least at the level of whole pages scanned).
--
-- This caused the postcondition assertion within _bt_advance_array_keys to
-- fail thusly:
--
-- TRAP: failed Assert("_bt_tuple_before_array_skeys(scan, pstate, tuple) == (!all_required_eqtype_sk_equal && !arrays_exhausted)")
--
-- At the time of writing, the "fallback on incremental advancement" path has
-- a design that doesn't properly account for non-required arrays.
prepare must_wraparound_high_order_column_inequality_nomatch as
with a as (
select i from generate_series(10000, 10500) i
)
select
thousand,
tenthous
from tenk1_dyn_saop
where thousand < 2 and tenthous = any (array[( select array_agg(i) from a)]);
execute must_wraparound_high_order_column_inequality_nomatch;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute must_wraparound_high_order_column_inequality_nomatch;
deallocate must_wraparound_high_order_column_inequality_nomatch;
-- Microbenchmarks
-- Index scan:
set enable_bitmapscan to off;
set enable_indexonlyscan to off;
set enable_indexscan to on;
-- Low cardinality
CREATE INDEX tenk1_dyn_saop_idx_lowcard ON tenk1_dyn_saop (two, four, twenty, hundred);
-- Limit 10:
select ctid, * from tenk1_dyn_saop
where
two in (0, 1)
and four in (0, 1, 2)
and twenty in (0, 1, 3)
and hundred in (0, 1, 5)
order by
two,
four,
twenty,
hundred
limit 10;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
select ctid, * from tenk1_dyn_saop
where
two in (0, 1)
and four in (0, 1, 2)
and twenty in (0, 1, 3)
and hundred in (0, 1, 5)
order by
two,
four,
twenty,
hundred
limit 10;
---------------------------------
-- non-SK_BT_REQFWD test cases --
---------------------------------
-- Index-only scan:
VACUUM (freeze,analyze) tenk1_dyn_saop;
set enable_indexonlyscan to on;
-- Four is omitted here:
prepare four_omitted as
select
count(*),
two,
twenty
from
tenk1_dyn_saop
where
two = 0
and twenty in (9, 10)
group by
two,
twenty;
execute four_omitted;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF, SUMMARY OFF)
execute four_omitted;
deallocate four_omitted;