
-- Test for create/drop database propagation.
-- This test is only executes for Postgres versions < 15.
-- For versions >= 15, pg15_create_drop_database_propagation.sql is used.
-- For versions >= 16, pg16_create_drop_database_propagation.sql is used.

-- Test the UDF that we use to issue database command during metadata sync.
SELECT pg_catalog.citus_internal_database_command(null);

CREATE ROLE test_db_commands WITH LOGIN;
ALTER SYSTEM SET citus.enable_manual_metadata_changes_for_user TO 'test_db_commands';
SELECT pg_reload_conf();
SELECT pg_sleep(0.1);
SET ROLE test_db_commands;

-- fails on null input
SELECT pg_catalog.citus_internal_database_command(null);

-- fails on non create / drop db command
SELECT pg_catalog.citus_internal_database_command('CREATE TABLE foo_bar(a int)');
SELECT pg_catalog.citus_internal_database_command('SELECT 1');
SELECT pg_catalog.citus_internal_database_command('asfsfdsg');
SELECT pg_catalog.citus_internal_database_command('');

RESET ROLE;
ALTER ROLE test_db_commands nocreatedb;
SET ROLE test_db_commands;

-- make sure that pg_catalog.citus_internal_database_command doesn't cause privilege escalation
SELECT pg_catalog.citus_internal_database_command('CREATE DATABASE no_permissions');

RESET ROLE;
DROP USER test_db_commands;
ALTER SYSTEM RESET citus.enable_manual_metadata_changes_for_user;
SELECT pg_reload_conf();
SELECT pg_sleep(0.1);

\set create_drop_db_tablespace :abs_srcdir '/tmp_check/ts3'
CREATE TABLESPACE create_drop_db_tablespace LOCATION :'create_drop_db_tablespace';

\c - - - :worker_1_port
\set create_drop_db_tablespace :abs_srcdir '/tmp_check/ts4'
CREATE TABLESPACE create_drop_db_tablespace LOCATION :'create_drop_db_tablespace';

\c - - - :worker_2_port
\set create_drop_db_tablespace :abs_srcdir '/tmp_check/ts5'
CREATE TABLESPACE create_drop_db_tablespace LOCATION :'create_drop_db_tablespace';

\c - - - :master_port
CREATE DATABASE local_database;

-- check that it's only created for coordinator
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;

DROP DATABASE local_database;

-- and is dropped
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;

\c - - - :worker_1_port
CREATE DATABASE local_database;

-- check that it's only created for coordinator
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;

DROP DATABASE local_database;

-- and is dropped
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;

\c - - - :master_port
create user create_drop_db_test_user;

set citus.enable_create_database_propagation=on;

-- Tests for create database propagation with template0 which should fail
CREATE DATABASE mydatabase
    WITH OWNER = create_drop_db_test_user
    TEMPLATE = 'template0'
            ENCODING = 'UTF8'
            CONNECTION LIMIT = 10
            TABLESPACE = create_drop_db_tablespace
            ALLOW_CONNECTIONS = true
            IS_TEMPLATE = false;

CREATE DATABASE mydatabase_1
    WITH template=template1
    OWNER = create_drop_db_test_user
            ENCODING = 'UTF8'
            CONNECTION LIMIT = 10
            TABLESPACE = create_drop_db_tablespace
            ALLOW_CONNECTIONS = true
            IS_TEMPLATE = false;

SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;

-- Test LC / LOCALE settings that don't match the ones provided in template db.
-- All should throw an error on the coordinator.
CREATE DATABASE lc_collate_test LC_COLLATE = 'C.UTF-8';
CREATE DATABASE lc_ctype_test LC_CTYPE = 'C.UTF-8';
CREATE DATABASE locale_test LOCALE = 'C.UTF-8';
CREATE DATABASE lc_collate_lc_ctype_test LC_COLLATE = 'C.UTF-8' LC_CTYPE = 'C.UTF-8';

-- Test LC / LOCALE settings that match the ones provided in template db.
CREATE DATABASE lc_collate_test LC_COLLATE = 'C';
CREATE DATABASE lc_ctype_test LC_CTYPE = 'C';
CREATE DATABASE locale_test LOCALE = 'C';
CREATE DATABASE lc_collate_lc_ctype_test LC_COLLATE = 'C' LC_CTYPE = 'C';

SELECT * FROM public.check_database_on_all_nodes('lc_collate_test') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('lc_ctype_test') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('locale_test') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('lc_collate_lc_ctype_test') ORDER BY node_type;

DROP DATABASE lc_collate_test;
DROP DATABASE lc_ctype_test;
DROP DATABASE locale_test;
DROP DATABASE lc_collate_lc_ctype_test;

-- ALTER TABLESPACE .. RENAME TO .. is not supported, so we need to rename it manually.
SELECT result FROM run_command_on_all_nodes(
  $$
  ALTER TABLESPACE create_drop_db_tablespace RENAME TO "ts-needs\!escape"
  $$
);

CREATE USER "role-needs\!escape";

CREATE DATABASE "db-needs\!escape" owner "role-needs\!escape" tablespace "ts-needs\!escape";

-- Rename it to make check_database_on_all_nodes happy.
-- Today we don't support ALTER DATABASE .. RENAME TO .., so need to propagate it manually.
SELECT result FROM run_command_on_all_nodes(
  $$
  ALTER DATABASE "db-needs\!escape" RENAME TO db_needs_escape
  $$
);

SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;

-- test database syncing after node addition

select 1 from citus_remove_node('localhost', :worker_2_port);

--test with is_template true and allow connections false
CREATE DATABASE mydatabase
            OWNER = create_drop_db_test_user
            CONNECTION LIMIT = 10
            ENCODING = 'UTF8'
            TABLESPACE = "ts-needs\!escape"
            ALLOW_CONNECTIONS = false
            IS_TEMPLATE = false;

SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;

SET citus.metadata_sync_mode to 'transactional';
select 1 from citus_add_node('localhost', :worker_2_port);

SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;

select 1 from citus_remove_node('localhost', :worker_2_port);

SET citus.metadata_sync_mode to 'nontransactional';
select 1 from citus_add_node('localhost', :worker_2_port);

RESET citus.metadata_sync_mode;

SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;

SELECT citus_disable_node_and_wait('localhost', :worker_1_port, true);

CREATE DATABASE test_node_activation;
SELECT 1 FROM citus_activate_node('localhost', :worker_1_port);

SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;
SELECT * FROM public.check_database_on_all_nodes('test_node_activation') ORDER BY node_type;

SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%DROP DATABASE%';
drop database mydatabase;

SET citus.log_remote_commands = false;

-- check that we actually drop the database
drop database mydatabase_1;

SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;

SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;

-- create a template database with all options set and allow connections false
CREATE DATABASE my_template_database
    WITH    OWNER = create_drop_db_test_user
            ENCODING = 'UTF8'
            TABLESPACE = "ts-needs\!escape"
            ALLOW_CONNECTIONS = false
            IS_TEMPLATE = true;

SELECT * FROM public.check_database_on_all_nodes('my_template_database') ORDER BY node_type;

--template databases could not be dropped so we need to change the template flag
SELECT result from run_command_on_all_nodes(
  $$
  UPDATE pg_database SET datistemplate = false WHERE datname = 'my_template_database'
  $$
) ORDER BY result;

SET citus.log_remote_commands = true;

set citus.grep_remote_commands = '%DROP DATABASE%';
drop database my_template_database;

SET citus.log_remote_commands = false;

SELECT * FROM public.check_database_on_all_nodes('my_template_database') ORDER BY node_type;

--tests for special characters in database name
set citus.enable_create_database_propagation=on;
SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%CREATE DATABASE%';

create database "mydatabase#1'2";

set citus.grep_remote_commands = '%DROP DATABASE%';
drop database if exists "mydatabase#1'2";

reset citus.grep_remote_commands;
reset citus.log_remote_commands;

-- it doesn't fail thanks to "if exists"
drop database if exists "mydatabase#1'2";

-- recreate it to verify that it's actually dropped
create database "mydatabase#1'2";
drop database "mydatabase#1'2";

-- second time we try to drop it, it fails due to lack of "if exists"
drop database "mydatabase#1'2";

\c - - - :worker_1_port

SET citus.enable_create_database_propagation TO ON;

-- show that dropping the database from workers is not allowed when citus.enable_create_database_propagation is on
DROP DATABASE db_needs_escape;

-- and the same applies to create database too
create database error_test;

\c - - - :master_port

SET citus.enable_create_database_propagation TO ON;

DROP DATABASE test_node_activation;
DROP DATABASE db_needs_escape;
DROP USER "role-needs\!escape";

-- drop database with force options test

create database db_force_test;

SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%DROP DATABASE%';

drop database db_force_test with (force);

reset citus.log_remote_commands;
reset citus.grep_remote_commands;

SELECT * FROM public.check_database_on_all_nodes('db_force_test') ORDER BY node_type;

-- test that we won't propagate non-distributed databases in citus_add_node

select 1 from citus_remove_node('localhost', :worker_2_port);
SET citus.enable_create_database_propagation TO off;
CREATE DATABASE non_distributed_db;
SET citus.enable_create_database_propagation TO on;
create database distributed_db;

select 1 from citus_add_node('localhost', :worker_2_port);

--non_distributed_db should not be propagated to worker_2
SELECT * FROM public.check_database_on_all_nodes('non_distributed_db') ORDER BY node_type;
--distributed_db should be propagated to worker_2
SELECT * FROM public.check_database_on_all_nodes('distributed_db') ORDER BY node_type;

--clean up resources created by this test
drop database distributed_db;

set citus.enable_create_database_propagation TO off;
drop database non_distributed_db;



-- test role grants on DATABASE in metadata sync

SELECT result from run_command_on_all_nodes(
  $$
  create database db_role_grants_test_non_distributed
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  revoke connect,temp,temporary,create  on database db_role_grants_test_non_distributed from public
  $$
) ORDER BY result;

SET citus.enable_create_database_propagation TO on;



CREATE ROLE db_role_grants_test_role_exists_on_node_2;


select 1 from citus_remove_node('localhost', :worker_2_port);

CREATE DATABASE db_role_grants_test;

revoke connect,temp,temporary,create  on database db_role_grants_test from public;

SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%CREATE ROLE%';
CREATE ROLE db_role_grants_test_role_missing_on_node_2;

RESET citus.log_remote_commands ;
RESET citus.grep_remote_commands;



SET citus.log_remote_commands = true;
set citus.grep_remote_commands = '%GRANT%';
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test to db_role_grants_test_role_exists_on_node_2;
grant CONNECT,TEMPORARY,CREATE  on DATABASE db_role_grants_test to db_role_grants_test_role_missing_on_node_2;



grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test_non_distributed to db_role_grants_test_role_exists_on_node_2;
grant CONNECT,TEMPORARY,CREATE  on DATABASE db_role_grants_test_non_distributed to db_role_grants_test_role_missing_on_node_2;

-- check the privileges before add_node for database db_role_grants_test,
--  role db_role_grants_test_role_exists_on_node_2

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CREATE')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CONNECT')
  $$
) ORDER BY result;

-- check the privileges before add_node for database db_role_grants_test,
--  role db_role_grants_test_role_missing_on_node_2

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CREATE')
  $$
) ORDER BY result;


SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CONNECT')
  $$
) ORDER BY result;

-- check the privileges before add_node  for database db_role_grants_test_non_distributed,
--  role db_role_grants_test_role_exists_on_node_2
SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test_non_distributed', 'CREATE')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test_non_distributed', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test_non_distributed', 'CONNECT')
  $$
) ORDER BY result;

-- check the privileges before add_node for database db_role_grants_test_non_distributed,
--  role db_role_grants_test_role_missing_on_node_2

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test_non_distributed', 'CREATE')
  $$
) ORDER BY result;


SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test_non_distributed', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test_non_distributed', 'CONNECT')
  $$
) ORDER BY result;


RESET citus.log_remote_commands;
RESET citus.grep_remote_commands;

select 1 from citus_add_node('localhost', :worker_2_port);


-- check the privileges after add_node for database db_role_grants_test,
--  role db_role_grants_test_role_exists_on_node_2

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CREATE')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test', 'CONNECT')
  $$
) ORDER BY result;

-- check the privileges after add_node for database db_role_grants_test,
--  role db_role_grants_test_role_missing_on_node_2

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CREATE')
  $$
) ORDER BY result;


SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test', 'CONNECT')
  $$
) ORDER BY result;

-- check the privileges after add_node for database db_role_grants_test_non_distributed,
--  role db_role_grants_test_role_exists_on_node_2
SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test_non_distributed', 'CREATE')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test_non_distributed', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_exists_on_node_2','db_role_grants_test_non_distributed', 'CONNECT')
  $$
) ORDER BY result;

-- check the privileges after add_node for database db_role_grants_test_non_distributed,
--  role db_role_grants_test_role_missing_on_node_2

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test_non_distributed', 'CREATE')
  $$
) ORDER BY result;


SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test_non_distributed', 'TEMPORARY')
  $$
) ORDER BY result;

SELECT result from run_command_on_all_nodes(
  $$
  select has_database_privilege('db_role_grants_test_role_missing_on_node_2','db_role_grants_test_non_distributed', 'CONNECT')
  $$
) ORDER BY result;

grant connect,temp,temporary,create  on database db_role_grants_test to public;

DROP DATABASE db_role_grants_test;

SELECT result from run_command_on_all_nodes(
  $$
  drop database db_role_grants_test_non_distributed
  $$
) ORDER BY result;
DROP ROLE db_role_grants_test_role_exists_on_node_2;
DROP ROLE db_role_grants_test_role_missing_on_node_2;




--clean up resources created by this test

-- DROP TABLESPACE is not supported, so we need to drop it manually.
SELECT result FROM run_command_on_all_nodes(
  $$
  drop tablespace "ts-needs\!escape"
  $$
);

drop user create_drop_db_test_user;
reset citus.enable_create_database_propagation;
