SET citus.enable_create_database_propagation TO ON;
SET client_min_messages TO WARNING;
SELECT 1 FROM citus_add_node('localhost', :master_port, 0);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

CREATE FUNCTION get_temp_databases_on_nodes()
RETURNS TEXT AS $func$
  SELECT array_agg(DISTINCT result ORDER BY result) AS temp_databases_on_nodes FROM run_command_on_all_nodes($$SELECT datname FROM pg_database WHERE datname LIKE 'citus_temp_database_%'$$) WHERE result != '';
$func$
LANGUAGE sql;
CREATE FUNCTION count_db_cleanup_records()
RETURNS TABLE(object_name TEXT, count INTEGER) AS $func$
  SELECT object_name, COUNT(*) FROM pg_dist_cleanup WHERE object_name LIKE 'citus_temp_database_%' GROUP BY object_name;
$func$
LANGUAGE sql;
CREATE FUNCTION ensure_no_temp_databases_on_any_nodes()
RETURNS BOOLEAN AS $func$
  SELECT bool_and(result::boolean) AS no_temp_databases_on_any_nodes FROM run_command_on_all_nodes($$SELECT COUNT(*)=0 FROM pg_database WHERE datname LIKE 'citus_temp_database_%'$$);
$func$
LANGUAGE sql;
-- cleanup any orphaned resources from previous runs
CALL citus_cleanup_orphaned_resources();
SET citus.next_operation_id TO 4000;
ALTER SYSTEM SET citus.defer_shard_delete_interval TO -1;
SELECT pg_reload_conf();
 pg_reload_conf
---------------------------------------------------------------------
 t
(1 row)

SELECT pg_sleep(0.1);
 pg_sleep
---------------------------------------------------------------------

(1 row)

SELECT citus.mitmproxy('conn.kill()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  connection to the remote node postgres@localhost:xxxxx failed with the following error: connection not open
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------

(1 row)

SELECT * FROM count_db_cleanup_records();
 object_name | count
---------------------------------------------------------------------
(0 rows)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

SELECT citus.mitmproxy('conn.onQuery(query="^CREATE DATABASE").cancel(' || pg_backend_pid() || ')');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  canceling statement due to user request
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------
 {citus_temp_database_4000_0}
(1 row)

SELECT * FROM count_db_cleanup_records();
        object_name         | count
---------------------------------------------------------------------
 citus_temp_database_4000_0 |     3
(1 row)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

SELECT citus.mitmproxy('conn.onQuery(query="^ALTER DATABASE").cancel(' || pg_backend_pid() || ')');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  canceling statement due to user request
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------
 {citus_temp_database_4001_0}
(1 row)

SELECT * FROM count_db_cleanup_records();
        object_name         | count
---------------------------------------------------------------------
 citus_temp_database_4001_0 |     3
(1 row)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

SELECT citus.mitmproxy('conn.onQuery(query="^BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED").kill()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  connection to the remote node postgres@localhost:xxxxx failed with the following error: connection not open
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------

(1 row)

SELECT * FROM count_db_cleanup_records();
 object_name | count
---------------------------------------------------------------------
(0 rows)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

SELECT citus.mitmproxy('conn.onQuery(query="^PREPARE TRANSACTION").kill()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  connection not open
CONTEXT:  while executing command on localhost:xxxxx
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------
 {citus_temp_database_4002_0}
(1 row)

SELECT * FROM count_db_cleanup_records();
        object_name         | count
---------------------------------------------------------------------
 citus_temp_database_4002_0 |     3
(1 row)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

SELECT citus.mitmproxy('conn.onQuery(query="^COMMIT PREPARED").kill()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
WARNING:  connection not open
CONTEXT:  while executing command on localhost:xxxxx
WARNING:  failed to commit transaction on localhost:xxxxx
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

-- not call citus_cleanup_orphaned_resources() but recover the prepared transactions this time
SELECT 1 FROM recover_prepared_transactions();
 ?column?
---------------------------------------------------------------------
        1
(1 row)

SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                                                                                                                                                                                    result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": {"datacl": null, "datname": "db1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "postgres", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": {"datacl": null, "datname": "db1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "postgres", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": {"datacl": null, "datname": "db1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "postgres", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

DROP DATABASE db1;
-- after recovering the prepared transactions, cleanup records should also be removed
SELECT * FROM count_db_cleanup_records();
 object_name | count
---------------------------------------------------------------------
(0 rows)

SELECT citus.mitmproxy('conn.onQuery(query="^SELECT citus_internal.acquire_citus_advisory_object_class_lock").kill()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  connection to the remote node postgres@localhost:xxxxx failed with the following error: connection not open
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------

(1 row)

SELECT * FROM count_db_cleanup_records();
 object_name | count
---------------------------------------------------------------------
(0 rows)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

SELECT citus.mitmproxy('conn.onParse(query="^WITH distributed_object_data").kill()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

CREATE DATABASE db1;
ERROR:  connection not open
CONTEXT:  while executing command on localhost:xxxxx
SELECT citus.mitmproxy('conn.allow()');
 mitmproxy
---------------------------------------------------------------------

(1 row)

SELECT get_temp_databases_on_nodes();
 get_temp_databases_on_nodes
---------------------------------------------------------------------
 {citus_temp_database_4004_0}
(1 row)

SELECT * FROM count_db_cleanup_records();
        object_name         | count
---------------------------------------------------------------------
 citus_temp_database_4004_0 |     3
(1 row)

CALL citus_cleanup_orphaned_resources();
SELECT ensure_no_temp_databases_on_any_nodes();
 ensure_no_temp_databases_on_any_nodes
---------------------------------------------------------------------
 t
(1 row)

SELECT * FROM public.check_database_on_all_nodes($$db1$$) ORDER BY node_type, result;
      node_type       |                                                              result
---------------------------------------------------------------------
 coordinator (local)  | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
 worker node (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
(3 rows)

CREATE DATABASE db1;
-- show that a successful database creation doesn't leave any pg_dist_cleanup records behind
SELECT * FROM count_db_cleanup_records();
 object_name | count
---------------------------------------------------------------------
(0 rows)

DROP DATABASE db1;
DROP FUNCTION get_temp_databases_on_nodes();
DROP FUNCTION ensure_no_temp_databases_on_any_nodes();
DROP FUNCTION count_db_cleanup_records();
SELECT 1 FROM citus_remove_node('localhost', :master_port);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

