1-- This file and its contents are licensed under the Apache License 2.0.
2-- Please see the included NOTICE for copyright information and
3-- LICENSE-APACHE for a copy of the license.
4\set TEST_DBNAME_2 :TEST_DBNAME _2
5\c :TEST_DBNAME :ROLE_SUPERUSER
6-- start bgw since they are stopped for tests by default
7SELECT _timescaledb_internal.start_background_workers();
8 start_background_workers
9--------------------------
10 t
11(1 row)
12
13CREATE DATABASE :TEST_DBNAME_2;
14\c :TEST_DBNAME_2 :ROLE_SUPERUSER
15\ir include/bgw_launcher_utils.sql
16-- This file and its contents are licensed under the Apache License 2.0.
17-- Please see the included NOTICE for copyright information and
18-- LICENSE-APACHE for a copy of the license.
19-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes
20-- to appear in pg_stat_activity.
21-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.
22-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.
23-- So we've added some bits about the version number as needed.
24CREATE VIEW worker_counts as SELECT count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Launcher') as launcher,
25count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,
26count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,
27count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler
28FROM pg_stat_activity;
29CREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
30$BODY$
31DECLARE
32r INTEGER;
33BEGIN
34FOR i in 1..10
35LOOP
36SELECT COUNT(*) from worker_counts where launcher = launcher_ct
37	AND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;
38if(r < 1) THEN
39  PERFORM pg_sleep(0.1);
40  PERFORM pg_stat_clear_snapshot();
41ELSE
42	--We have the correct counts!
43  RETURN TRUE;
44END IF;
45END LOOP;
46RETURN FALSE;
47END
48$BODY$;
49CREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS
50$BODY$
51DECLARE
52  r INTEGER;
53BEGIN
54  FOR i in 1.._ticks
55  LOOP
56    SELECT count(*)
57    FROM pg_stat_activity
58    WHERE
59      application_name = 'TimescaleDB Background Worker Scheduler' AND
60      datname = _datname
61    INTO r;
62
63    IF(r <> _count) THEN
64      PERFORM pg_sleep(0.1);
65      PERFORM pg_stat_clear_snapshot();
66    ELSE
67      RETURN 'BGW Scheduler found.';
68    END IF;
69
70  END LOOP;
71
72  RETURN 'BGW Scheduler NOT found.';
73
74END
75$BODY$;
76-- When we've connected to test db 2, we should be able to see the cluster launcher
77-- and the scheduler for test db in pg_stat_activity
78-- but test db 2 shouldn't have a scheduler because ext not created yet
79SELECT wait_worker_counts(1,1,0,0);
80 wait_worker_counts
81--------------------
82 t
83(1 row)
84
85-- Now create the extension in test db 2
86SET client_min_messages = ERROR;
87CREATE EXTENSION timescaledb CASCADE;
88RESET client_min_messages;
89SELECT wait_worker_counts(1,1,1,0);
90 wait_worker_counts
91--------------------
92 t
93(1 row)
94
95DROP DATABASE :TEST_DBNAME;
96-- Now the db_scheduler for test db should have disappeared
97SELECT wait_worker_counts(1,0,1,0);
98 wait_worker_counts
99--------------------
100 t
101(1 row)
102
103-- Now let's restart the scheduler in test db 2 and make sure our backend_start changed
104SELECT backend_start as orig_backend_start
105FROM pg_stat_activity
106WHERE application_name = 'TimescaleDB Background Worker Scheduler'
107AND datname = :'TEST_DBNAME_2' \gset
108-- We'll do this in a txn so that we can see that the worker locks on our txn before continuing
109BEGIN;
110SELECT _timescaledb_internal.restart_background_workers();
111 restart_background_workers
112----------------------------
113 t
114(1 row)
115
116SELECT wait_worker_counts(1,0,1,0);
117 wait_worker_counts
118--------------------
119 t
120(1 row)
121
122SELECT (backend_start > :'orig_backend_start'::timestamptz) backend_start_changed,
123(wait_event = 'virtualxid') wait_event_changed
124FROM pg_stat_activity
125WHERE application_name = 'TimescaleDB Background Worker Scheduler'
126AND datname = :'TEST_DBNAME_2';
127 backend_start_changed | wait_event_changed
128-----------------------+--------------------
129 t                     | t
130(1 row)
131
132COMMIT;
133SELECT wait_worker_counts(1,0,1,0);
134 wait_worker_counts
135--------------------
136 t
137(1 row)
138
139SELECT (wait_event IS DISTINCT FROM 'virtualxid') wait_event_changed
140FROM pg_stat_activity
141WHERE application_name = 'TimescaleDB Background Worker Scheduler'
142AND datname = :'TEST_DBNAME_2';
143 wait_event_changed
144--------------------
145 t
146(1 row)
147
148-- Test stop
149SELECT _timescaledb_internal.stop_background_workers();
150 stop_background_workers
151-------------------------
152 t
153(1 row)
154
155SELECT wait_worker_counts(1,0,0,0);
156 wait_worker_counts
157--------------------
158 t
159(1 row)
160
161-- Make sure it doesn't break if we stop twice in a row
162SELECT _timescaledb_internal.stop_background_workers();
163 stop_background_workers
164-------------------------
165 t
166(1 row)
167
168SELECT wait_worker_counts(1,0,0,0);
169 wait_worker_counts
170--------------------
171 t
172(1 row)
173
174-- test start
175SELECT _timescaledb_internal.start_background_workers();
176 start_background_workers
177--------------------------
178 t
179(1 row)
180
181SELECT wait_worker_counts(1,0,1,0);
182 wait_worker_counts
183--------------------
184 t
185(1 row)
186
187-- make sure start is idempotent
188SELECT backend_start as orig_backend_start
189FROM pg_stat_activity
190WHERE application_name = 'TimescaleDB Background Worker Scheduler'
191AND datname = :'TEST_DBNAME_2' \gset
192-- Since we're doing idempotency tests, we're also going to exercise our queue and start 20 times
193SELECT _timescaledb_internal.start_background_workers() as start_background_workers, * FROM generate_series(1,20);
194 start_background_workers | generate_series
195--------------------------+-----------------
196 t                        |               1
197 t                        |               2
198 t                        |               3
199 t                        |               4
200 t                        |               5
201 t                        |               6
202 t                        |               7
203 t                        |               8
204 t                        |               9
205 t                        |              10
206 t                        |              11
207 t                        |              12
208 t                        |              13
209 t                        |              14
210 t                        |              15
211 t                        |              16
212 t                        |              17
213 t                        |              18
214 t                        |              19
215 t                        |              20
216(20 rows)
217
218-- Here we're waiting to see if something shows up in pg_stat_activity,
219--  so we have to condition our loop in the opposite way. We'll only wait
220--  half a second in total as well so that tests don't take too long.
221CREATE FUNCTION wait_equals(TIMESTAMPTZ, TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
222$BODY$
223DECLARE
224r BOOLEAN;
225BEGIN
226FOR i in 1..5
227LOOP
228SELECT (backend_start = $1::timestamptz) backend_start_unchanged
229FROM pg_stat_activity
230WHERE application_name = 'TimescaleDB Background Worker Scheduler'
231AND datname = $2 into r;
232if(r) THEN
233  PERFORM pg_sleep(0.1);
234  PERFORM pg_stat_clear_snapshot();
235ELSE
236  RETURN FALSE;
237END IF;
238END LOOP;
239RETURN TRUE;
240END
241$BODY$;
242select wait_equals(:'orig_backend_start', :'TEST_DBNAME_2');
243 wait_equals
244-------------
245 t
246(1 row)
247
248-- Make sure restart starts a worker even if it is stopped
249SELECT _timescaledb_internal.stop_background_workers();
250 stop_background_workers
251-------------------------
252 t
253(1 row)
254
255SELECT wait_worker_counts(1,0,0,0);
256 wait_worker_counts
257--------------------
258 t
259(1 row)
260
261SELECT _timescaledb_internal.restart_background_workers();
262 restart_background_workers
263----------------------------
264 t
265(1 row)
266
267SELECT wait_worker_counts(1,0,1,0);
268 wait_worker_counts
269--------------------
270 t
271(1 row)
272
273-- Make sure drop extension statement restarts the worker and on rollback it keeps running
274-- Now let's restart the scheduler and make sure our backend_start changed
275SELECT backend_start as orig_backend_start
276FROM pg_stat_activity
277WHERE application_name = 'TimescaleDB Background Worker Scheduler'
278AND datname = :'TEST_DBNAME_2' \gset
279BEGIN;
280DROP EXTENSION timescaledb;
281SELECT wait_worker_counts(1,0,1,0);
282 wait_worker_counts
283--------------------
284 t
285(1 row)
286
287ROLLBACK;
288CREATE FUNCTION wait_greater(TIMESTAMPTZ,TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
289$BODY$
290DECLARE
291r BOOLEAN;
292BEGIN
293FOR i in 1..10
294LOOP
295SELECT (backend_start > $1::timestamptz) backend_start_changed
296FROM pg_stat_activity
297WHERE application_name = 'TimescaleDB Background Worker Scheduler'
298AND datname = $2 into r;
299if(NOT r) THEN
300  PERFORM pg_sleep(0.1);
301  PERFORM pg_stat_clear_snapshot();
302ELSE
303  RETURN TRUE;
304END IF;
305END LOOP;
306RETURN FALSE;
307END
308$BODY$;
309SELECT wait_greater(:'orig_backend_start',:'TEST_DBNAME_2');
310 wait_greater
311--------------
312 t
313(1 row)
314
315-- Make sure canceling the launcher backend causes a restart of schedulers
316SELECT backend_start as orig_backend_start
317FROM pg_stat_activity
318WHERE application_name = 'TimescaleDB Background Worker Scheduler'
319AND datname = :'TEST_DBNAME_2' \gset
320SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE application_name = 'TimescaleDB Background Worker Launcher';
321 pg_cancel_backend
322-------------------
323 t
324(1 row)
325
326SELECT wait_worker_counts(1,0,1,0);
327 wait_worker_counts
328--------------------
329 t
330(1 row)
331
332SELECT wait_greater(:'orig_backend_start', :'TEST_DBNAME_2');
333 wait_greater
334--------------
335 t
336(1 row)
337
338-- Make sure running pre_restore function stops background workers
339SELECT timescaledb_pre_restore();
340 timescaledb_pre_restore
341-------------------------
342 t
343(1 row)
344
345SELECT wait_worker_counts(1,0,0,0);
346 wait_worker_counts
347--------------------
348 t
349(1 row)
350
351-- Make sure a restart with restoring on first starts the background worker
352BEGIN;
353SELECT _timescaledb_internal.restart_background_workers();
354 restart_background_workers
355----------------------------
356 t
357(1 row)
358
359SELECT wait_worker_counts(1,0,1,0);
360 wait_worker_counts
361--------------------
362 t
363(1 row)
364
365COMMIT;
366-- Then the worker dies when it sees that restoring is on after the txn commits
367SELECT wait_worker_counts(1,0,0,0);
368 wait_worker_counts
369--------------------
370 t
371(1 row)
372
373--And post_restore starts them
374BEGIN;
375SELECT timescaledb_post_restore();
376 timescaledb_post_restore
377--------------------------
378 t
379(1 row)
380
381SELECT wait_worker_counts(1,0,1,0);
382 wait_worker_counts
383--------------------
384 t
385(1 row)
386
387COMMIT;
388-- And they stay started
389SELECT wait_worker_counts(1,0,1,0);
390 wait_worker_counts
391--------------------
392 t
393(1 row)
394
395-- Make sure dropping the extension means that the scheduler is stopped
396BEGIN;
397DROP EXTENSION timescaledb;
398COMMIT;
399SELECT wait_worker_counts(1,0,0,0);
400 wait_worker_counts
401--------------------
402 t
403(1 row)
404
405-- Test that background workers are stopped with DROP OWNED
406ALTER ROLE :ROLE_DEFAULT_PERM_USER WITH SUPERUSER;
407\c :TEST_DBNAME_2 :ROLE_DEFAULT_PERM_USER
408SET client_min_messages = ERROR;
409CREATE EXTENSION timescaledb CASCADE;
410RESET client_min_messages;
411-- Make sure there is 1 launcher and 1 bgw in test db 2
412SELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);
413 wait_worker_counts
414--------------------
415 t
416(1 row)
417
418-- drop a non-owner of the extension results in no change to worker counts
419DROP OWNED BY :ROLE_DEFAULT_PERM_USER_2;
420SELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);
421 wait_worker_counts
422--------------------
423 t
424(1 row)
425
426-- drop of owner of extension results in extension drop and a stop to the bgw
427DROP OWNED BY :ROLE_DEFAULT_PERM_USER;
428-- The worker in test db 2 is dead. Note that 0s are respected
429SELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=>0, scheduler2_ct=>0, template1_ct=>0);
430 wait_worker_counts
431--------------------
432 t
433(1 row)
434
435\c :TEST_DBNAME_2 :ROLE_SUPERUSER
436ALTER ROLE :ROLE_DEFAULT_PERM_USER WITH NOSUPERUSER;
437-- Connect to the template1 database
438\c template1
439\ir include/bgw_launcher_utils.sql
440-- This file and its contents are licensed under the Apache License 2.0.
441-- Please see the included NOTICE for copyright information and
442-- LICENSE-APACHE for a copy of the license.
443-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes
444-- to appear in pg_stat_activity.
445-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.
446-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.
447-- So we've added some bits about the version number as needed.
448CREATE VIEW worker_counts as SELECT count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Launcher') as launcher,
449count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,
450count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,
451count(*) filter (WHERE application_name = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler
452FROM pg_stat_activity;
453CREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
454$BODY$
455DECLARE
456r INTEGER;
457BEGIN
458FOR i in 1..10
459LOOP
460SELECT COUNT(*) from worker_counts where launcher = launcher_ct
461	AND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;
462if(r < 1) THEN
463  PERFORM pg_sleep(0.1);
464  PERFORM pg_stat_clear_snapshot();
465ELSE
466	--We have the correct counts!
467  RETURN TRUE;
468END IF;
469END LOOP;
470RETURN FALSE;
471END
472$BODY$;
473CREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS
474$BODY$
475DECLARE
476  r INTEGER;
477BEGIN
478  FOR i in 1.._ticks
479  LOOP
480    SELECT count(*)
481    FROM pg_stat_activity
482    WHERE
483      application_name = 'TimescaleDB Background Worker Scheduler' AND
484      datname = _datname
485    INTO r;
486
487    IF(r <> _count) THEN
488      PERFORM pg_sleep(0.1);
489      PERFORM pg_stat_clear_snapshot();
490    ELSE
491      RETURN 'BGW Scheduler found.';
492    END IF;
493
494  END LOOP;
495
496  RETURN 'BGW Scheduler NOT found.';
497
498END
499$BODY$;
500BEGIN;
501-- Then create extension there in a txn and make sure we see a scheduler start
502SET client_min_messages = ERROR;
503CREATE EXTENSION timescaledb CASCADE;
504RESET client_min_messages;
505SELECT wait_worker_counts(1,0,0,1);
506 wait_worker_counts
507--------------------
508 t
509(1 row)
510
511COMMIT;
512-- End our transaction and it should immediately exit because it's a template database.
513SELECT wait_worker_counts(1,0,0,0);
514 wait_worker_counts
515--------------------
516 t
517(1 row)
518
519-- Clean up the template database, removing our test utilities etc
520\ir include/bgw_launcher_utils_cleanup.sql
521-- This file and its contents are licensed under the Apache License 2.0.
522-- Please see the included NOTICE for copyright information and
523-- LICENSE-APACHE for a copy of the license.
524DROP FUNCTION wait_worker_counts(integer, integer, integer, integer);
525DROP VIEW worker_counts;
526DROP FUNCTION wait_for_bgw_scheduler(name,int,int);
527\c :TEST_DBNAME_2
528-- Now try creating a DB from a template with the extension already installed.
529-- Make sure we see a scheduler start.
530CREATE DATABASE :TEST_DBNAME;
531SELECT wait_worker_counts(1,1,0,0);
532 wait_worker_counts
533--------------------
534 t
535(1 row)
536
537DROP DATABASE :TEST_DBNAME;
538-- Now make sure that there's no race between create database and create extension.
539-- Although to be honest, this race probably wouldn't manifest in this test.
540\c template1
541DROP EXTENSION timescaledb;
542\c :TEST_DBNAME_2
543CREATE DATABASE :TEST_DBNAME;
544\c :TEST_DBNAME
545SET client_min_messages = ERROR;
546CREATE EXTENSION timescaledb;
547RESET client_min_messages;
548\c :TEST_DBNAME_2
549SELECT wait_worker_counts(1,1,0,0);
550 wait_worker_counts
551--------------------
552 t
553(1 row)
554
555-- test rename database
556CREATE DATABASE db_rename_test;
557\c db_rename_test :ROLE_SUPERUSER
558SET client_min_messages=error;
559CREATE EXTENSION timescaledb;
560\c :TEST_DBNAME_2 :ROLE_SUPERUSER
561SELECT wait_for_bgw_scheduler('db_rename_test');
562 wait_for_bgw_scheduler
563------------------------
564 BGW Scheduler found.
565(1 row)
566
567ALTER DATABASE db_rename_test RENAME TO db_rename_test2;
568WARNING:  you need to manually restart any running background workers after this command
569DROP DATABASE db_rename_test2;
570-- test create database with timescaledb database as template
571SELECT wait_for_bgw_scheduler(:'TEST_DBNAME');
572 wait_for_bgw_scheduler
573------------------------
574 BGW Scheduler found.
575(1 row)
576
577CREATE DATABASE db_from_template WITH TEMPLATE :TEST_DBNAME;
578SELECT wait_for_bgw_scheduler(:'TEST_DBNAME');
579 wait_for_bgw_scheduler
580------------------------
581 BGW Scheduler found.
582(1 row)
583
584DROP DATABASE db_from_template;
585-- test alter database set tablespace
586SET client_min_messages TO error;
587DROP TABLESPACE IF EXISTS tablespace1;
588RESET client_min_messages;
589CREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;
590SELECT wait_for_bgw_scheduler(:'TEST_DBNAME');
591 wait_for_bgw_scheduler
592------------------------
593 BGW Scheduler found.
594(1 row)
595
596ALTER DATABASE :TEST_DBNAME SET TABLESPACE tablespace1;
597WARNING:  you may need to manually restart any running background workers after this command
598-- clean up additional database
599\c :TEST_DBNAME :ROLE_SUPERUSER
600DROP DATABASE :TEST_DBNAME_2;
601