1 -- directory paths and dlsuffix are passed to us in environment variables
2 \getenv libdir PG_LIBDIR
3 \getenv dlsuffix PG_DLSUFFIX
4 \set regresslib :libdir '/regress' :dlsuffix
5 -- Function to assist with verifying EXPLAIN which includes costs. A series
6 -- of bool flags allows control over which portions are masked out
7 CREATE FUNCTION explain_mask_costs(query text, do_analyze bool,
8 hide_costs bool, hide_row_est bool, hide_width bool) RETURNS setof text
15 IF do_analyze = true THEN
21 -- avoid jit related output by disabling it
25 EXECUTE format('explain (analyze %s, costs on, summary off, timing off) %s',
28 IF hide_costs = true THEN
29 ln := regexp_replace(ln, 'cost=\d+\.\d\d\.\.\d+\.\d\d', 'cost=N..N');
32 IF hide_row_est = true THEN
33 -- don't use 'g' so that we leave the actual rows intact
34 ln := regexp_replace(ln, 'rows=\d+', 'rows=N');
37 IF hide_width = true THEN
38 ln := regexp_replace(ln, 'width=\d+', 'width=N');
48 SELECT num_nonnulls(NULL);
54 SELECT num_nonnulls('1');
60 SELECT num_nonnulls(NULL::text);
66 SELECT num_nonnulls(NULL::text, NULL::int);
72 SELECT num_nonnulls(1, 2, NULL::text, NULL::point, '', int8 '9', 1.0 / NULL);
78 SELECT num_nonnulls(VARIADIC '{1,2,NULL,3}'::int[]);
84 SELECT num_nonnulls(VARIADIC '{"1","2","3","4"}'::text[]);
90 SELECT num_nonnulls(VARIADIC ARRAY(SELECT CASE WHEN i <> 40 THEN i END FROM generate_series(1, 100) i));
96 SELECT num_nulls(NULL);
102 SELECT num_nulls('1');
108 SELECT num_nulls(NULL::text);
114 SELECT num_nulls(NULL::text, NULL::int);
120 SELECT num_nulls(1, 2, NULL::text, NULL::point, '', int8 '9', 1.0 / NULL);
126 SELECT num_nulls(VARIADIC '{1,2,NULL,3}'::int[]);
132 SELECT num_nulls(VARIADIC '{"1","2","3","4"}'::text[]);
138 SELECT num_nulls(VARIADIC ARRAY(SELECT CASE WHEN i <> 40 THEN i END FROM generate_series(1, 100) i));
145 SELECT num_nonnulls(VARIADIC NULL::text[]);
151 SELECT num_nonnulls(VARIADIC '{}'::int[]);
157 SELECT num_nulls(VARIADIC NULL::text[]);
163 SELECT num_nulls(VARIADIC '{}'::int[]);
169 -- should fail, one or more arguments is required
170 SELECT num_nonnulls();
171 ERROR: function num_nonnulls() does not exist
172 LINE 1: SELECT num_nonnulls();
174 HINT: No function matches the given name and argument types. You might need to add explicit type casts.
176 ERROR: function num_nulls() does not exist
177 LINE 1: SELECT num_nulls();
179 HINT: No function matches the given name and argument types. You might need to add explicit type casts.
181 -- canonicalize_path()
183 CREATE FUNCTION test_canonicalize_path(text)
186 LANGUAGE C STRICT IMMUTABLE;
187 SELECT test_canonicalize_path('/');
188 test_canonicalize_path
189 ------------------------
193 SELECT test_canonicalize_path('/./abc/def/');
194 test_canonicalize_path
195 ------------------------
199 SELECT test_canonicalize_path('/./../abc/def');
200 test_canonicalize_path
201 ------------------------
205 SELECT test_canonicalize_path('/./../../abc/def/');
206 test_canonicalize_path
207 ------------------------
211 SELECT test_canonicalize_path('/abc/.././def/ghi');
212 test_canonicalize_path
213 ------------------------
217 SELECT test_canonicalize_path('/abc/./../def/ghi//');
218 test_canonicalize_path
219 ------------------------
223 SELECT test_canonicalize_path('/abc/def/../..');
224 test_canonicalize_path
225 ------------------------
229 SELECT test_canonicalize_path('/abc/def/../../..');
230 test_canonicalize_path
231 ------------------------
235 SELECT test_canonicalize_path('/abc/def/../../../../ghi/jkl');
236 test_canonicalize_path
237 ------------------------
241 SELECT test_canonicalize_path('.');
242 test_canonicalize_path
243 ------------------------
247 SELECT test_canonicalize_path('./');
248 test_canonicalize_path
249 ------------------------
253 SELECT test_canonicalize_path('./abc/..');
254 test_canonicalize_path
255 ------------------------
259 SELECT test_canonicalize_path('abc/../');
260 test_canonicalize_path
261 ------------------------
265 SELECT test_canonicalize_path('abc/../def');
266 test_canonicalize_path
267 ------------------------
271 SELECT test_canonicalize_path('..');
272 test_canonicalize_path
273 ------------------------
277 SELECT test_canonicalize_path('../abc/def');
278 test_canonicalize_path
279 ------------------------
283 SELECT test_canonicalize_path('../abc/..');
284 test_canonicalize_path
285 ------------------------
289 SELECT test_canonicalize_path('../abc/../def');
290 test_canonicalize_path
291 ------------------------
295 SELECT test_canonicalize_path('../abc/../../def/ghi');
296 test_canonicalize_path
297 ------------------------
301 SELECT test_canonicalize_path('./abc/./def/.');
302 test_canonicalize_path
303 ------------------------
307 SELECT test_canonicalize_path('./abc/././def/.');
308 test_canonicalize_path
309 ------------------------
313 SELECT test_canonicalize_path('./abc/./def/.././ghi/../../../jkl/mno');
314 test_canonicalize_path
315 ------------------------
320 -- pg_log_backend_memory_contexts()
322 -- Memory contexts are logged and they are not returned to the function.
323 -- Furthermore, their contents can vary depending on the timing. However,
324 -- we can at least verify that the code doesn't fail, and that the
325 -- permissions are set properly.
327 SELECT pg_log_backend_memory_contexts(pg_backend_pid());
328 pg_log_backend_memory_contexts
329 --------------------------------
333 SELECT pg_log_backend_memory_contexts(pid) FROM pg_stat_activity
334 WHERE backend_type = 'checkpointer';
335 pg_log_backend_memory_contexts
336 --------------------------------
340 CREATE ROLE regress_log_memory;
341 SELECT has_function_privilege('regress_log_memory',
342 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- no
343 has_function_privilege
344 ------------------------
348 GRANT EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer)
349 TO regress_log_memory;
350 SELECT has_function_privilege('regress_log_memory',
351 'pg_log_backend_memory_contexts(integer)', 'EXECUTE'); -- yes
352 has_function_privilege
353 ------------------------
357 SET ROLE regress_log_memory;
358 SELECT pg_log_backend_memory_contexts(pg_backend_pid());
359 pg_log_backend_memory_contexts
360 --------------------------------
365 REVOKE EXECUTE ON FUNCTION pg_log_backend_memory_contexts(integer)
366 FROM regress_log_memory;
367 DROP ROLE regress_log_memory;
369 -- Test some built-in SRFs
371 -- The outputs of these are variable, so we can't just print their results
372 -- directly, but we can at least verify that the code doesn't fail.
374 select setting as segsize
375 from pg_settings where name = 'wal_segment_size'
377 select count(*) > 0 as ok from pg_ls_waldir();
383 -- Test ProjectSet as well as FunctionScan
384 select count(*) > 0 as ok from (select pg_ls_waldir()) ss;
390 -- Test not-run-to-completion cases.
391 select * from pg_ls_waldir() limit 0;
392 name | size | modification
393 ------+------+--------------
396 select count(*) > 0 as ok from (select * from pg_ls_waldir() limit 1) ss;
402 select (w).size = :segsize as ok
403 from (select pg_ls_waldir() w) ss where length((w).name) = 24 limit 1;
409 select count(*) >= 0 as ok from pg_ls_archive_statusdir();
416 select length(pg_read_file('postmaster.pid')) > 20;
422 select length(pg_read_file('postmaster.pid', 1, 20));
429 select pg_read_file('does not exist'); -- error
430 ERROR: could not open file "does not exist" for reading: No such file or directory
431 select pg_read_file('does not exist', true) IS NULL; -- ok
437 -- Test invalid argument
438 select pg_read_file('does not exist', 0, -1); -- error
439 ERROR: requested length cannot be negative
440 select pg_read_file('does not exist', 0, -1, true); -- error
441 ERROR: requested length cannot be negative
442 -- pg_read_binary_file()
443 select length(pg_read_binary_file('postmaster.pid')) > 20;
449 select length(pg_read_binary_file('postmaster.pid', 1, 20));
456 select pg_read_binary_file('does not exist'); -- error
457 ERROR: could not open file "does not exist" for reading: No such file or directory
458 select pg_read_binary_file('does not exist', true) IS NULL; -- ok
464 -- Test invalid argument
465 select pg_read_binary_file('does not exist', 0, -1); -- error
466 ERROR: requested length cannot be negative
467 select pg_read_binary_file('does not exist', 0, -1, true); -- error
468 ERROR: requested length cannot be negative
470 select size > 20, isdir from pg_stat_file('postmaster.pid');
477 select * from (select pg_ls_dir('.') a) a where a = 'base' limit 1;
483 -- Test missing_ok (second argument)
484 select pg_ls_dir('does not exist', false, false); -- error
485 ERROR: could not open directory "does not exist": No such file or directory
486 select pg_ls_dir('does not exist', true, false); -- ok
491 -- Test include_dot_dirs (third argument)
492 select count(*) = 1 as dot_found
493 from pg_ls_dir('.', false, true) as ls where ls = '.';
499 select count(*) = 1 as dot_found
500 from pg_ls_dir('.', false, false) as ls where ls = '.';
506 -- pg_timezone_names()
507 select * from (select (pg_timezone_names()).name) ptn where name='UTC' limit 1;
513 -- pg_tablespace_databases()
514 select count(*) > 0 from
515 (select pg_tablespace_databases(oid) as pts from pg_tablespace
516 where spcname = 'pg_default') pts
517 join pg_database db on pts.pts = db.oid;
524 -- Test replication slot directory functions
526 CREATE ROLE regress_slot_dir_funcs;
527 -- Not available by default.
528 SELECT has_function_privilege('regress_slot_dir_funcs',
529 'pg_ls_logicalsnapdir()', 'EXECUTE');
530 has_function_privilege
531 ------------------------
535 SELECT has_function_privilege('regress_slot_dir_funcs',
536 'pg_ls_logicalmapdir()', 'EXECUTE');
537 has_function_privilege
538 ------------------------
542 SELECT has_function_privilege('regress_slot_dir_funcs',
543 'pg_ls_replslotdir(text)', 'EXECUTE');
544 has_function_privilege
545 ------------------------
549 GRANT pg_monitor TO regress_slot_dir_funcs;
550 -- Role is now part of pg_monitor, so these are available.
551 SELECT has_function_privilege('regress_slot_dir_funcs',
552 'pg_ls_logicalsnapdir()', 'EXECUTE');
553 has_function_privilege
554 ------------------------
558 SELECT has_function_privilege('regress_slot_dir_funcs',
559 'pg_ls_logicalmapdir()', 'EXECUTE');
560 has_function_privilege
561 ------------------------
565 SELECT has_function_privilege('regress_slot_dir_funcs',
566 'pg_ls_replslotdir(text)', 'EXECUTE');
567 has_function_privilege
568 ------------------------
572 DROP ROLE regress_slot_dir_funcs;
574 -- Test adding a support function to a subject function
576 CREATE FUNCTION my_int_eq(int, int) RETURNS bool
577 LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
579 -- By default, planner does not think that's selective
581 SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
582 WHERE my_int_eq(a.unique2, 42);
584 ----------------------------------------------
586 Hash Cond: (b.unique1 = a.unique1)
587 -> Seq Scan on tenk1 b
589 -> Seq Scan on tenk1 a
590 Filter: my_int_eq(unique2, 42)
593 -- With support function that knows it's int4eq, we get a different plan
594 CREATE FUNCTION test_support_func(internal)
596 AS :'regresslib', 'test_support_func'
598 ALTER FUNCTION my_int_eq(int, int) SUPPORT test_support_func;
600 SELECT * FROM tenk1 a JOIN tenk1 b ON a.unique1 = b.unique1
601 WHERE my_int_eq(a.unique2, 42);
603 -------------------------------------------------
605 -> Seq Scan on tenk1 a
606 Filter: my_int_eq(unique2, 42)
607 -> Index Scan using tenk1_unique1 on tenk1 b
608 Index Cond: (unique1 = a.unique1)
611 -- Also test non-default rowcount estimate
612 CREATE FUNCTION my_gen_series(int, int) RETURNS SETOF integer
613 LANGUAGE internal STRICT IMMUTABLE PARALLEL SAFE
614 AS $$generate_series_int4$$
615 SUPPORT test_support_func;
617 SELECT * FROM tenk1 a JOIN my_gen_series(1,1000) g ON a.unique1 = g;
619 ----------------------------------------
621 Hash Cond: (g.g = a.unique1)
622 -> Function Scan on my_gen_series g
624 -> Seq Scan on tenk1 a
628 SELECT * FROM tenk1 a JOIN my_gen_series(1,10) g ON a.unique1 = g;
630 -------------------------------------------------
632 -> Function Scan on my_gen_series g
633 -> Index Scan using tenk1_unique1 on tenk1 a
634 Index Cond: (unique1 = g.g)
638 -- Test the SupportRequestRows support function for generate_series_timestamp()
640 -- Ensure the row estimate matches the actual rows
641 SELECT explain_mask_costs($$
642 SELECT * FROM generate_series(TIMESTAMPTZ '2024-02-01', TIMESTAMPTZ '2024-03-01', INTERVAL '1 day') g(s);$$,
643 true, true, false, true);
645 ------------------------------------------------------------------------------------------
646 Function Scan on generate_series g (cost=N..N rows=30 width=N) (actual rows=30 loops=1)
649 -- As above but with generate_series_timestamp
650 SELECT explain_mask_costs($$
651 SELECT * FROM generate_series(TIMESTAMP '2024-02-01', TIMESTAMP '2024-03-01', INTERVAL '1 day') g(s);$$,
652 true, true, false, true);
654 ------------------------------------------------------------------------------------------
655 Function Scan on generate_series g (cost=N..N rows=30 width=N) (actual rows=30 loops=1)
658 -- As above but with generate_series_timestamptz_at_zone()
659 SELECT explain_mask_costs($$
660 SELECT * FROM generate_series(TIMESTAMPTZ '2024-02-01', TIMESTAMPTZ '2024-03-01', INTERVAL '1 day', 'UTC') g(s);$$,
661 true, true, false, true);
663 ------------------------------------------------------------------------------------------
664 Function Scan on generate_series g (cost=N..N rows=30 width=N) (actual rows=30 loops=1)
667 -- Ensure the estimated and actual row counts match when the range isn't
668 -- evenly divisible by the step
669 SELECT explain_mask_costs($$
670 SELECT * FROM generate_series(TIMESTAMPTZ '2024-02-01', TIMESTAMPTZ '2024-03-01', INTERVAL '7 day') g(s);$$,
671 true, true, false, true);
673 ----------------------------------------------------------------------------------------
674 Function Scan on generate_series g (cost=N..N rows=5 width=N) (actual rows=5 loops=1)
677 -- Ensure the estimates match when step is decreasing
678 SELECT explain_mask_costs($$
679 SELECT * FROM generate_series(TIMESTAMPTZ '2024-03-01', TIMESTAMPTZ '2024-02-01', INTERVAL '-1 day') g(s);$$,
680 true, true, false, true);
682 ------------------------------------------------------------------------------------------
683 Function Scan on generate_series g (cost=N..N rows=30 width=N) (actual rows=30 loops=1)
686 -- Ensure an empty range estimates 1 row
687 SELECT explain_mask_costs($$
688 SELECT * FROM generate_series(TIMESTAMPTZ '2024-03-01', TIMESTAMPTZ '2024-02-01', INTERVAL '1 day') g(s);$$,
689 true, true, false, true);
691 ----------------------------------------------------------------------------------------
692 Function Scan on generate_series g (cost=N..N rows=1 width=N) (actual rows=0 loops=1)
695 -- Ensure we get the default row estimate for infinity values
696 SELECT explain_mask_costs($$
697 SELECT * FROM generate_series(TIMESTAMPTZ '-infinity', TIMESTAMPTZ 'infinity', INTERVAL '1 day') g(s);$$,
698 false, true, false, true);
700 -------------------------------------------------------------------
701 Function Scan on generate_series g (cost=N..N rows=1000 width=N)
704 -- Ensure the row estimate behaves correctly when step size is zero.
705 -- We expect generate_series_timestamp() to throw the error rather than in
706 -- the support function.
707 SELECT * FROM generate_series(TIMESTAMPTZ '2024-02-01', TIMESTAMPTZ '2024-03-01', INTERVAL '0 day') g(s);
708 ERROR: step size cannot equal zero
709 -- Test functions for control data
710 SELECT count(*) > 0 AS ok FROM pg_control_checkpoint();
716 SELECT count(*) > 0 AS ok FROM pg_control_init();
722 SELECT count(*) > 0 AS ok FROM pg_control_recovery();
728 SELECT count(*) > 0 AS ok FROM pg_control_system();
734 -- pg_split_walfile_name, pg_walfile_name & pg_walfile_name_offset
735 SELECT * FROM pg_split_walfile_name(NULL);
736 segment_number | timeline_id
737 ----------------+-------------
741 SELECT * FROM pg_split_walfile_name('invalid');
742 ERROR: invalid WAL file name "invalid"
743 SELECT segment_number > 0 AS ok_segment_number, timeline_id
744 FROM pg_split_walfile_name('000000010000000100000000');
745 ok_segment_number | timeline_id
746 -------------------+-------------
750 SELECT segment_number > 0 AS ok_segment_number, timeline_id
751 FROM pg_split_walfile_name('ffffffFF00000001000000af');
752 ok_segment_number | timeline_id
753 -------------------+-------------
757 SELECT setting::int8 AS segment_size
759 WHERE name = 'wal_segment_size'
761 SELECT segment_number, file_offset
762 FROM pg_walfile_name_offset('0/0'::pg_lsn + :segment_size),
763 pg_split_walfile_name(file_name);
764 segment_number | file_offset
765 ----------------+-------------
769 SELECT segment_number, file_offset
770 FROM pg_walfile_name_offset('0/0'::pg_lsn + :segment_size + 1),
771 pg_split_walfile_name(file_name);
772 segment_number | file_offset
773 ----------------+-------------
777 SELECT segment_number, file_offset = :segment_size - 1
778 FROM pg_walfile_name_offset('0/0'::pg_lsn + :segment_size - 1),
779 pg_split_walfile_name(file_name);
780 segment_number | ?column?
781 ----------------+----------
785 -- pg_current_logfile
786 CREATE ROLE regress_current_logfile;
787 -- not available by default
788 SELECT has_function_privilege('regress_current_logfile',
789 'pg_current_logfile()', 'EXECUTE');
790 has_function_privilege
791 ------------------------
795 GRANT pg_monitor TO regress_current_logfile;
796 -- role has privileges of pg_monitor and can execute the function
797 SELECT has_function_privilege('regress_current_logfile',
798 'pg_current_logfile()', 'EXECUTE');
799 has_function_privilege
800 ------------------------
804 DROP ROLE regress_current_logfile;
805 -- pg_column_toast_chunk_id
806 CREATE TABLE test_chunk_id (a TEXT, b TEXT STORAGE EXTERNAL);
807 INSERT INTO test_chunk_id VALUES ('x', repeat('x', 8192));
808 SELECT t.relname AS toastrel FROM pg_class c
809 LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
810 WHERE c.relname = 'test_chunk_id'
812 SELECT pg_column_toast_chunk_id(a) IS NULL,
813 pg_column_toast_chunk_id(b) IN (SELECT chunk_id FROM pg_toast.:toastrel)
816 ----------+----------
820 DROP TABLE test_chunk_id;
821 DROP FUNCTION explain_mask_costs(text, bool, bool, bool, bool);