-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
\set TEST_DBNAME_2 :TEST_DBNAME _2
\c :TEST_DBNAME :ROLE_SUPERUSER
-- start bgw since they are stopped for tests by default
SELECT _timescaledb_functions.start_background_workers();
 start_background_workers 
--------------------------
 t

CREATE DATABASE :TEST_DBNAME_2;
\c :TEST_DBNAME_2 :ROLE_SUPERUSER
\ir include/bgw_launcher_utils.sql
-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes
-- to appear in pg_stat_activity.
-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.
-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.
-- So we've added some bits about the version number as needed.
CREATE VIEW worker_counts as
SELECT count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Launcher') as launcher,
       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,
       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,
       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler
  FROM pg_stat_activity;
CREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
$BODY$
DECLARE
r INTEGER;
BEGIN
FOR i in 1..10
LOOP
SELECT COUNT(*) from worker_counts where launcher = launcher_ct
	AND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;
if(r < 1) THEN
  PERFORM pg_sleep(0.1);
  PERFORM pg_stat_clear_snapshot();
ELSE
	--We have the correct counts!
  RETURN TRUE;
END IF;
END LOOP;
RETURN FALSE;
END
$BODY$;
CREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS
$BODY$
DECLARE
  r INTEGER;
BEGIN
  FOR i in 1.._ticks
  LOOP
    SELECT count(*)
    FROM pg_stat_activity
    WHERE
      application_name = 'TimescaleDB Background Worker Scheduler' AND
      datname = _datname
    INTO r;

    IF(r <> _count) THEN
      PERFORM pg_sleep(0.1);
      PERFORM pg_stat_clear_snapshot();
    ELSE
      RETURN 'BGW Scheduler found.';
    END IF;

  END LOOP;

  RETURN 'BGW Scheduler NOT found.';

END
$BODY$;
CREATE PROCEDURE kill_database_backends(_datname NAME) LANGUAGE PLPGSQL AS
$BODY$
DECLARE
  r INTEGER;
BEGIN
  FOR i in 1..100
  LOOP
    SELECT count(pg_terminate_backend(pg_stat_activity.pid))
    FROM pg_stat_activity
    WHERE
      datname = _datname
          AND pg_stat_activity.pid <> pg_backend_pid()
    INTO r;

    IF(r = 0) THEN
        RETURN;
    END IF;

    PERFORM pg_sleep(0.1);
    PERFORM pg_stat_clear_snapshot();
  END LOOP;

  RAISE 'Failed to terminate backends';

END
$BODY$;
-- When we've connected to test db 2, we should be able to see the cluster launcher
-- and the scheduler for test db in pg_stat_activity
-- but test db 2 shouldn't have a scheduler because ext not created yet
SELECT wait_worker_counts(1,1,0,0);
 wait_worker_counts 
--------------------
 t

-- Now create the extension in test db 2
SET client_min_messages = ERROR;
CREATE EXTENSION timescaledb CASCADE;
RESET client_min_messages;
SELECT wait_worker_counts(1,1,1,0);
 wait_worker_counts 
--------------------
 t

DROP DATABASE :TEST_DBNAME WITH (FORCE);
-- Now the db_scheduler for test db should have disappeared
SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

-- Now let's restart the scheduler in test db 2 and make sure our backend_start changed
SELECT backend_start as orig_backend_start
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = :'TEST_DBNAME_2' \gset
-- We'll do this in a txn so that we can see that the worker locks on our txn before continuing
BEGIN;
SELECT _timescaledb_functions.restart_background_workers();
 restart_background_workers 
----------------------------
 t

SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

SELECT (backend_start > :'orig_backend_start'::timestamptz) backend_start_changed,
(wait_event = 'virtualxid') wait_event_changed
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = :'TEST_DBNAME_2';
 backend_start_changed | wait_event_changed 
-----------------------+--------------------
 t                     | t

COMMIT;
SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

SELECT (wait_event IS DISTINCT FROM 'virtualxid') wait_event_changed
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = :'TEST_DBNAME_2';
 wait_event_changed 
--------------------
 t

-- Test stop
SELECT _timescaledb_functions.stop_background_workers();
 stop_background_workers 
-------------------------
 t

SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

-- Make sure it doesn't break if we stop twice in a row
SELECT _timescaledb_functions.stop_background_workers();
 stop_background_workers 
-------------------------
 t

SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

-- test start
SELECT _timescaledb_functions.start_background_workers();
 start_background_workers 
--------------------------
 t

SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

-- make sure start is idempotent
SELECT backend_start as orig_backend_start
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = :'TEST_DBNAME_2' \gset
-- Since we're doing idempotency tests, we're also going to exercise our queue and start 20 times
SELECT _timescaledb_functions.start_background_workers() as start_background_workers, * FROM generate_series(1,20);
 start_background_workers | generate_series 
--------------------------+-----------------
 t                        |               1
 t                        |               2
 t                        |               3
 t                        |               4
 t                        |               5
 t                        |               6
 t                        |               7
 t                        |               8
 t                        |               9
 t                        |              10
 t                        |              11
 t                        |              12
 t                        |              13
 t                        |              14
 t                        |              15
 t                        |              16
 t                        |              17
 t                        |              18
 t                        |              19
 t                        |              20

-- Here we're waiting to see if something shows up in pg_stat_activity,
--  so we have to condition our loop in the opposite way. We'll only wait
--  half a second in total as well so that tests don't take too long.
CREATE FUNCTION wait_equals(TIMESTAMPTZ, TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
$BODY$
DECLARE
r BOOLEAN;
BEGIN
FOR i in 1..5
LOOP
SELECT (backend_start = $1::timestamptz) backend_start_unchanged
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = $2 into r;
if(r) THEN
  PERFORM pg_sleep(0.1);
  PERFORM pg_stat_clear_snapshot();
ELSE
  RETURN FALSE;
END IF;
END LOOP;
RETURN TRUE;
END
$BODY$;
select wait_equals(:'orig_backend_start', :'TEST_DBNAME_2');
 wait_equals 
-------------
 t

-- Make sure restart starts a worker even if it is stopped
SELECT _timescaledb_functions.stop_background_workers();
 stop_background_workers 
-------------------------
 t

SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

SELECT _timescaledb_functions.restart_background_workers();
 restart_background_workers 
----------------------------
 t

SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

-- Make sure drop extension statement restarts the worker and on rollback it keeps running
-- Now let's restart the scheduler and make sure our backend_start changed
SELECT backend_start as orig_backend_start
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = :'TEST_DBNAME_2' \gset
BEGIN;
DROP EXTENSION timescaledb;
SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

ROLLBACK;
CREATE FUNCTION wait_greater(TIMESTAMPTZ,TEXT) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
$BODY$
DECLARE
r BOOLEAN;
BEGIN
FOR i in 1..10
LOOP
SELECT (backend_start > $1::timestamptz) backend_start_changed
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = $2 into r;
if(NOT r) THEN
  PERFORM pg_sleep(0.1);
  PERFORM pg_stat_clear_snapshot();
ELSE
  RETURN TRUE;
END IF;
END LOOP;
RETURN FALSE;
END
$BODY$;
SELECT wait_greater(:'orig_backend_start',:'TEST_DBNAME_2');
 wait_greater 
--------------
 t

-- Make sure canceling the launcher backend causes a restart of schedulers
SELECT backend_start as orig_backend_start
FROM pg_stat_activity
WHERE backend_type = 'TimescaleDB Background Worker Scheduler'
AND datname = :'TEST_DBNAME_2' \gset
SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE backend_type = 'TimescaleDB Background Worker Launcher';
 pg_cancel_backend 
-------------------
 t

SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

SELECT wait_greater(:'orig_backend_start', :'TEST_DBNAME_2');
 wait_greater 
--------------
 t

-- Make sure running pre_restore function stops background workers
SELECT timescaledb_pre_restore();
 timescaledb_pre_restore 
-------------------------
 t

SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

-- Make sure a restart with restoring on first starts the background worker
BEGIN;
SELECT _timescaledb_functions.restart_background_workers();
 restart_background_workers 
----------------------------
 t

SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

COMMIT;
-- Then the worker dies when it sees that restoring is on after the txn commits
SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

--And post_restore starts them
BEGIN;
SELECT timescaledb_post_restore();
 timescaledb_post_restore 
--------------------------
 t

SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

COMMIT;
-- And they stay started
SELECT wait_worker_counts(1,0,1,0);
 wait_worker_counts 
--------------------
 t

-- Make sure dropping the extension means that the scheduler is stopped
BEGIN;
DROP EXTENSION timescaledb;
COMMIT;
SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

-- Test that background workers are stopped with DROP OWNED
ALTER ROLE :ROLE_DEFAULT_PERM_USER WITH SUPERUSER;
\c :TEST_DBNAME_2 :ROLE_DEFAULT_PERM_USER
SET client_min_messages = ERROR;
CREATE EXTENSION timescaledb CASCADE;
RESET client_min_messages;
-- Make sure there is 1 launcher and 1 bgw in test db 2
SELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);
 wait_worker_counts 
--------------------
 t

-- drop a non-owner of the extension results in no change to worker counts
DROP OWNED BY :ROLE_DEFAULT_PERM_USER_2;
SELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=> 0, scheduler2_ct=>1, template1_ct=>0);
 wait_worker_counts 
--------------------
 t

-- drop of owner of extension results in extension drop and a stop to the bgw
DROP OWNED BY :ROLE_DEFAULT_PERM_USER;
-- The worker in test db 2 is dead. Note that 0s are respected
SELECT wait_worker_counts(launcher_ct=>1, scheduler1_ct=>0, scheduler2_ct=>0, template1_ct=>0);
 wait_worker_counts 
--------------------
 t

\c :TEST_DBNAME_2 :ROLE_SUPERUSER
ALTER ROLE :ROLE_DEFAULT_PERM_USER WITH NOSUPERUSER;
-- Connect to the template1 database
\c template1
\ir include/bgw_launcher_utils.sql
-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- Note on testing: need a couple wrappers that pg_sleep in a loop to wait for changes
-- to appear in pg_stat_activity.
-- Further Note: PG 9.6 changed what appeared in pg_stat_activity, so the launcher doesn't actually show up.
-- we can still test its interactions with its children, but can't test some of the things specific to the launcher.
-- So we've added some bits about the version number as needed.
CREATE VIEW worker_counts as
SELECT count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Launcher') as launcher,
       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME') as single_scheduler,
       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = :'TEST_DBNAME_2') as single_2_scheduler,
       count(*) filter (WHERE backend_type = 'TimescaleDB Background Worker Scheduler' AND datname = 'template1') as template1_scheduler
  FROM pg_stat_activity;
CREATE FUNCTION wait_worker_counts(launcher_ct INTEGER,  scheduler1_ct INTEGER, scheduler2_ct INTEGER, template1_ct INTEGER) RETURNS BOOLEAN LANGUAGE PLPGSQL AS
$BODY$
DECLARE
r INTEGER;
BEGIN
FOR i in 1..10
LOOP
SELECT COUNT(*) from worker_counts where launcher = launcher_ct
	AND single_scheduler = scheduler1_ct AND single_2_scheduler = scheduler2_ct and template1_scheduler = template1_ct into r;
if(r < 1) THEN
  PERFORM pg_sleep(0.1);
  PERFORM pg_stat_clear_snapshot();
ELSE
	--We have the correct counts!
  RETURN TRUE;
END IF;
END LOOP;
RETURN FALSE;
END
$BODY$;
CREATE FUNCTION wait_for_bgw_scheduler(_datname NAME, _count INT DEFAULT 1, _ticks INT DEFAULT 10) RETURNS TEXT LANGUAGE PLPGSQL AS
$BODY$
DECLARE
  r INTEGER;
BEGIN
  FOR i in 1.._ticks
  LOOP
    SELECT count(*)
    FROM pg_stat_activity
    WHERE
      application_name = 'TimescaleDB Background Worker Scheduler' AND
      datname = _datname
    INTO r;

    IF(r <> _count) THEN
      PERFORM pg_sleep(0.1);
      PERFORM pg_stat_clear_snapshot();
    ELSE
      RETURN 'BGW Scheduler found.';
    END IF;

  END LOOP;

  RETURN 'BGW Scheduler NOT found.';

END
$BODY$;
CREATE PROCEDURE kill_database_backends(_datname NAME) LANGUAGE PLPGSQL AS
$BODY$
DECLARE
  r INTEGER;
BEGIN
  FOR i in 1..100
  LOOP
    SELECT count(pg_terminate_backend(pg_stat_activity.pid))
    FROM pg_stat_activity
    WHERE
      datname = _datname
          AND pg_stat_activity.pid <> pg_backend_pid()
    INTO r;

    IF(r = 0) THEN
        RETURN;
    END IF;

    PERFORM pg_sleep(0.1);
    PERFORM pg_stat_clear_snapshot();
  END LOOP;

  RAISE 'Failed to terminate backends';

END
$BODY$;
BEGIN;
-- Then create extension there in a txn and make sure we see a scheduler start
SET client_min_messages = ERROR;
CREATE EXTENSION timescaledb CASCADE;
RESET client_min_messages;
SELECT wait_worker_counts(1,0,0,1);
 wait_worker_counts 
--------------------
 t

COMMIT;
-- End our transaction and it should immediately exit because it's a template database.
SELECT wait_worker_counts(1,0,0,0);
 wait_worker_counts 
--------------------
 t

\c :TEST_DBNAME_2
-- Now try creating a DB from a template with the extension already installed.
-- Make sure we see a scheduler start.
CREATE DATABASE :TEST_DBNAME;
SELECT wait_worker_counts(1,1,0,0);
 wait_worker_counts 
--------------------
 t

DROP DATABASE :TEST_DBNAME WITH (FORCE);
-- Now make sure that there's no race between create database and create extension.
-- Although to be honest, this race probably wouldn't manifest in this test.
\c template1
DROP EXTENSION timescaledb;
\c :TEST_DBNAME_2
CREATE DATABASE :TEST_DBNAME;
\c :TEST_DBNAME
SET client_min_messages = ERROR;
CREATE EXTENSION timescaledb;
RESET client_min_messages;
\c :TEST_DBNAME_2
SELECT wait_worker_counts(1,1,0,0);
 wait_worker_counts 
--------------------
 t

-- test rename database
CREATE DATABASE db_rename_test;
\c db_rename_test :ROLE_SUPERUSER
SET client_min_messages=error;
CREATE EXTENSION timescaledb;
\c :TEST_DBNAME_2 :ROLE_SUPERUSER
SELECT wait_for_bgw_scheduler('db_rename_test');
 wait_for_bgw_scheduler 
------------------------
 BGW Scheduler found.

ALTER DATABASE db_rename_test RENAME TO db_rename_test2;
WARNING:  you need to manually restart any running background workers after this command
DROP DATABASE db_rename_test2 WITH (FORCE);
-- test create database with timescaledb database as template
SELECT wait_for_bgw_scheduler(:'TEST_DBNAME');
 wait_for_bgw_scheduler 
------------------------
 BGW Scheduler found.

CREATE DATABASE db_from_template WITH TEMPLATE :TEST_DBNAME;
SELECT wait_for_bgw_scheduler(:'TEST_DBNAME');
 wait_for_bgw_scheduler 
------------------------
 BGW Scheduler found.

DROP DATABASE db_from_template WITH (FORCE);
-- test alter database set tablespace
SET client_min_messages TO error;
DROP TABLESPACE IF EXISTS tablespace1;
RESET client_min_messages;
CREATE TABLESPACE tablespace1 OWNER :ROLE_DEFAULT_PERM_USER LOCATION :TEST_TABLESPACE1_PATH;
-- Stop background worker before we change the tablespace of the database (otherwise, the database might be used)
SELECT wait_for_bgw_scheduler(:'TEST_DBNAME');
 wait_for_bgw_scheduler 
------------------------
 BGW Scheduler found.

-- Connect to TEST_DBNAME (_timescaledb_functions.stop_background_workers() is not available in TEST_DBNAME_2)
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT _timescaledb_functions.stop_background_workers();
 stop_background_workers 
-------------------------
 t

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE backend_type = 'TimescaleDB Background Worker Launcher';
 pg_terminate_backend 
----------------------
 t

\c :TEST_DBNAME_2 :ROLE_SUPERUSER
-- make sure nobody is using it
REVOKE CONNECT ON DATABASE :TEST_DBNAME FROM public;
CALL kill_database_backends(:'TEST_DBNAME');
-- Change tablespace
ALTER DATABASE :TEST_DBNAME SET TABLESPACE tablespace1;
WARNING:  you may need to manually restart any running background workers after this command
-- tear down test and clean up additional database
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT _timescaledb_functions.stop_background_workers() \gset
REVOKE CONNECT ON DATABASE :TEST_DBNAME_2 FROM public;
CALL kill_database_backends(:'TEST_DBNAME_2');
SELECT * FROM pg_stat_activity WHERE datname = :'TEST_DBNAME_2';
 datid | datname | pid | leader_pid | usesysid | usename | application_name | client_addr | client_hostname | client_port | backend_start | xact_start | query_start | state_change | wait_event_type | wait_event | state | backend_xid | backend_xmin | query_id | query | backend_type 
-------+---------+-----+------------+----------+---------+------------------+-------------+-----------------+-------------+---------------+------------+-------------+--------------+-----------------+------------+-------+-------------+--------------+----------+-------+--------------

DROP DATABASE :TEST_DBNAME_2 WITH (force);
-- Clean up the template database, removing our test utilities etc
\c template1
\ir include/bgw_launcher_utils_cleanup.sql
-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
DROP FUNCTION wait_worker_counts(integer, integer, integer, integer);
DROP VIEW worker_counts;
DROP FUNCTION wait_for_bgw_scheduler(name,int,int);
DROP PROCEDURE kill_database_backends(name);
