diff --git a/sql/ddl_api.sql b/sql/ddl_api.sql index bbe6cb37e..03f6befa0 100644 --- a/sql/ddl_api.sql +++ b/sql/ddl_api.sql @@ -200,3 +200,8 @@ AS '@MODULE_PATHNAME@', 'ts_data_node_block_new_chunks' LANGUAGE C VOLATILE; -- allow chunks for all distributed hypertables CREATE OR REPLACE FUNCTION allow_new_chunks(data_node_name NAME, hypertable REGCLASS = NULL) RETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_data_node_allow_new_chunks' LANGUAGE C VOLATILE; + +-- Execute query on a specified list of data nodes. By default node_list is NULL, which means +-- to execute the query on every data node +CREATE OR REPLACE FUNCTION distributed_exec(query TEXT, node_list name[] = NULL) RETURNS VOID +AS '@MODULE_PATHNAME@', 'ts_distributed_exec' LANGUAGE C VOLATILE; diff --git a/src/cross_module_fn.c b/src/cross_module_fn.c index a723717d4..47d05f456 100644 --- a/src/cross_module_fn.c +++ b/src/cross_module_fn.c @@ -63,6 +63,7 @@ TS_FUNCTION_INFO_V1(ts_dist_remove_id); TS_FUNCTION_INFO_V1(ts_dist_set_peer_id); TS_FUNCTION_INFO_V1(ts_dist_remote_hypertable_info); TS_FUNCTION_INFO_V1(ts_dist_validate_as_data_node); +TS_FUNCTION_INFO_V1(ts_distributed_exec); Datum ts_add_drop_chunks_policy(PG_FUNCTION_ARGS) @@ -234,6 +235,13 @@ ts_dist_validate_as_data_node(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +Datum +ts_distributed_exec(PG_FUNCTION_ARGS) +{ + ts_cm_functions->distributed_exec(fcinfo); + PG_RETURN_VOID(); +} + /* * stub function to trigger aggregate util functions. */ @@ -630,6 +638,7 @@ TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = { .data_node_ping = error_no_default_fn_pg_community, .detach_data_node = error_no_default_fn_pg_community, .data_node_set_block_new_chunks = data_node_set_block_new_chunks_default, + .distributed_exec = error_no_default_fn_pg_community, .set_chunk_default_data_node = error_no_default_fn_pg_community, .show_chunk = error_no_default_fn_pg_community, .create_chunk = error_no_default_fn_pg_community, diff --git a/src/cross_module_fn.h b/src/cross_module_fn.h index 0127acb1b..ded3d9126 100644 --- a/src/cross_module_fn.h +++ b/src/cross_module_fn.h @@ -145,6 +145,7 @@ typedef struct CrossModuleFunctions Oid newer_than_type, bool cascade, bool cascades_to_materializations, bool verbose, List *data_node_oids); + PGFunction distributed_exec; } CrossModuleFunctions; extern TSDLLEXPORT CrossModuleFunctions *ts_cm_functions; diff --git a/test/expected/extension.out b/test/expected/extension.out index 6aec6a1f4..bc115010a 100644 --- a/test/expected/extension.out +++ b/test/expected/extension.out @@ -35,6 +35,7 @@ ORDER BY proname; detach_data_node detach_tablespace detach_tablespaces + distributed_exec drop_chunks first get_telemetry_report @@ -65,5 +66,5 @@ ORDER BY proname; timescaledb_fdw_validator timescaledb_post_restore timescaledb_pre_restore -(51 rows) +(52 rows) diff --git a/test/runner.sh b/test/runner.sh index 598f23217..99efc788c 100755 --- a/test/runner.sh +++ b/test/runner.sh @@ -58,7 +58,7 @@ if [[ ! -f ${TEST_OUTPUT_DIR}/.pg_init ]]; then SET client_min_messages=ERROR; ALTER USER ${TEST_ROLE_SUPERUSER} WITH SUPERUSER; ALTER USER ${TEST_ROLE_CLUSTER_SUPERUSER} WITH SUPERUSER; - ALTER USER ${TEST_ROLE_1} WITH CREATEDB; + ALTER USER ${TEST_ROLE_1} WITH CREATEDB CREATEROLE; ALTER USER ${TEST_ROLE_2} WITH CREATEDB; ALTER USER ${TEST_ROLE_3} WITH CREATEDB PASSWORD '${TEST_ROLE_3_PASS}'; EOF diff --git a/tsl/src/data_node.c b/tsl/src/data_node.c index 97cc146c1..cda5cd363 100644 --- a/tsl/src/data_node.c +++ b/tsl/src/data_node.c @@ -555,7 +555,7 @@ data_node_add_internal(PG_FUNCTION_ARGS, bool set_distid) bool database_created = false; bool extension_created = false; - if (set_distid && dist_util_membership() == DIST_MEMBER_BACKEND) + if (set_distid && dist_util_membership() == DIST_MEMBER_DATA_NODE) ereport(ERROR, (errcode(ERRCODE_TS_DATA_NODE_ASSIGNMENT_ALREADY_EXISTS), (errmsg("unable to assign data nodes from an existing distributed database")))); @@ -603,7 +603,7 @@ data_node_add_internal(PG_FUNCTION_ARGS, bool set_distid) if (set_distid) { - if (dist_util_membership() != DIST_MEMBER_FRONTEND) + if (dist_util_membership() != DIST_MEMBER_ACCESS_NODE) dist_util_set_as_frontend(); add_distributed_id_to_data_node(node_name, diff --git a/tsl/src/dist_util.c b/tsl/src/dist_util.c index dcddc2910..642f08ecc 100644 --- a/tsl/src/dist_util.c +++ b/tsl/src/dist_util.c @@ -56,9 +56,9 @@ dist_util_membership(void) if (isnull) return DIST_MEMBER_NONE; else if (uuid_matches(dist_id, ts_telemetry_metadata_get_uuid())) - return DIST_MEMBER_FRONTEND; + return DIST_MEMBER_ACCESS_NODE; else - return DIST_MEMBER_BACKEND; + return DIST_MEMBER_DATA_NODE; } void diff --git a/tsl/src/dist_util.h b/tsl/src/dist_util.h index 5586f97fa..ac1221336 100644 --- a/tsl/src/dist_util.h +++ b/tsl/src/dist_util.h @@ -11,9 +11,9 @@ typedef enum DistUtilMembershipStatus { - DIST_MEMBER_NONE, /* Database doesn't belong to a distributed database */ - DIST_MEMBER_BACKEND, /* Database is a backend node */ - DIST_MEMBER_FRONTEND /* Database is a frontend node */ + DIST_MEMBER_NONE, /* Database doesn't belong to a distributed database */ + DIST_MEMBER_DATA_NODE, /* Database is a backend node */ + DIST_MEMBER_ACCESS_NODE /* Database is a frontend node */ } DistUtilMembershipStatus; DistUtilMembershipStatus dist_util_membership(void); diff --git a/tsl/src/init.c b/tsl/src/init.c index 37c26c07d..dda3d96f5 100644 --- a/tsl/src/init.c +++ b/tsl/src/init.c @@ -53,6 +53,7 @@ #include "remote/txn_id.h" #include "remote/txn_resolve.h" #include "remote/dist_copy.h" +#include "remote/dist_commands.h" #include "process_utility.h" #include "dist_util.h" #include "remote/connection.h" @@ -256,6 +257,7 @@ CrossModuleFunctions tsl_cm_functions = { .remove_from_distributed_db = NULL, .remote_hypertable_info = error_not_supported_default_fn, .validate_as_data_node = NULL, + .distributed_exec = error_not_supported_default_fn, #else .add_data_node = data_node_add, .delete_data_node = data_node_delete, @@ -287,6 +289,7 @@ CrossModuleFunctions tsl_cm_functions = { .remote_hypertable_info = dist_util_remote_hypertable_info, .validate_as_data_node = validate_data_node_settings, .drop_chunks_on_data_nodes = chunk_drop_remote_chunks, + .distributed_exec = ts_dist_cmd_exec, #endif .cache_syscache_invalidate = cache_syscache_invalidate, }; diff --git a/tsl/src/remote/dist_commands.c b/tsl/src/remote/dist_commands.c index 9b30e0839..289470780 100644 --- a/tsl/src/remote/dist_commands.c +++ b/tsl/src/remote/dist_commands.c @@ -4,6 +4,9 @@ * LICENSE-TIMESCALE for a copy of the license. */ #include +#include +#include +#include #include @@ -11,6 +14,7 @@ #include "remote/dist_txn.h" #include "remote/connection_cache.h" #include "data_node.h" +#include "dist_util.h" #include "miscadmin.h" typedef struct DistPreparedStmt @@ -235,3 +239,32 @@ ts_dist_cmd_close_prepared_command(PreparedDistCmd *command) list_free_deep(command); } + +Datum +ts_dist_cmd_exec(PG_FUNCTION_ARGS) +{ + const char *query = TextDatumGetCString(PG_GETARG_DATUM(0)); + ArrayType *data_nodes = PG_ARGISNULL(1) ? NULL : PG_GETARG_ARRAYTYPE_P(1); + DistCmdResult *result; + List *data_node_list; + const char *search_path; + + if (dist_util_membership() != DIST_MEMBER_ACCESS_NODE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function must be run on the access node only"))); + + if (data_nodes == NULL) + data_node_list = data_node_get_node_name_list(); + else + data_node_list = data_node_array_to_node_name_list(data_nodes); + + search_path = GetConfigOption("search_path", false, false); + result = ts_dist_cmd_invoke_on_data_nodes_using_search_path(query, search_path, data_node_list); + if (result) + ts_dist_cmd_close_response(result); + + list_free(data_node_list); + + PG_RETURN_VOID(); +} diff --git a/tsl/src/remote/dist_commands.h b/tsl/src/remote/dist_commands.h index 801e40c4e..6f84249d8 100644 --- a/tsl/src/remote/dist_commands.h +++ b/tsl/src/remote/dist_commands.h @@ -35,4 +35,6 @@ extern DistCmdResult *ts_dist_cmd_invoke_prepared_command(PreparedDistCmd *comma extern void ts_dist_cmd_close_prepared_command(PreparedDistCmd *command); +extern Datum ts_dist_cmd_exec(PG_FUNCTION_ARGS); + #endif diff --git a/tsl/test/expected/dist_commands.out b/tsl/test/expected/dist_commands.out index ab5f8b2fb..61e7c42ca 100644 --- a/tsl/test/expected/dist_commands.out +++ b/tsl/test/expected/dist_commands.out @@ -2,38 +2,42 @@ -- Please see the included NOTICE for copyright information and -- LICENSE-TIMESCALE for a copy of the license. \c :TEST_DBNAME :ROLE_SUPERUSER +-- Support for execute_sql_and_filter_server_name_on_error() +\unset ECHO +psql:include/remote_exec.sql:5: NOTICE: schema "test" already exists, skipping +psql:include/filter_exec.sql:5: NOTICE: schema "test" already exists, skipping SET ROLE :ROLE_1; -SELECT * FROM add_data_node('data_node1', - database => 'data_node1', +SELECT * FROM add_data_node('data_node_1', + database => 'data_node_1', bootstrap_user => :'ROLE_CLUSTER_SUPERUSER'); - node_name | host | port | database | node_created | database_created | extension_created -------------+-----------+-------+------------+--------------+------------------+------------------- - data_node1 | localhost | 15432 | data_node1 | t | t | t + node_name | host | port | database | node_created | database_created | extension_created +-------------+-----------+-------+-------------+--------------+------------------+------------------- + data_node_1 | localhost | 15432 | data_node_1 | t | t | t (1 row) -SELECT * FROM add_data_node('data_node2', - database => 'data_node2', +SELECT * FROM add_data_node('data_node_2', + database => 'data_node_2', bootstrap_user => :'ROLE_CLUSTER_SUPERUSER'); - node_name | host | port | database | node_created | database_created | extension_created -------------+-----------+-------+------------+--------------+------------------+------------------- - data_node2 | localhost | 15432 | data_node2 | t | t | t + node_name | host | port | database | node_created | database_created | extension_created +-------------+-----------+-------+-------------+--------------+------------------+------------------- + data_node_2 | localhost | 15432 | data_node_2 | t | t | t (1 row) -SELECT * FROM add_data_node('data_node3', - database => 'data_node3', +SELECT * FROM add_data_node('data_node_3', + database => 'data_node_3', bootstrap_user => :'ROLE_CLUSTER_SUPERUSER'); - node_name | host | port | database | node_created | database_created | extension_created -------------+-----------+-------+------------+--------------+------------------+------------------- - data_node3 | localhost | 15432 | data_node3 | t | t | t + node_name | host | port | database | node_created | database_created | extension_created +-------------+-----------+-------+-------------+--------------+------------------+------------------- + data_node_3 | localhost | 15432 | data_node_3 | t | t | t (1 row) \des+ - List of foreign servers - Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description -------------+-------------+----------------------+-------------------+------+---------+-------------------------------------------------------+------------- - data_node1 | test_role_1 | timescaledb_fdw | | | | (host 'localhost', port '15432', dbname 'data_node1') | - data_node2 | test_role_1 | timescaledb_fdw | | | | (host 'localhost', port '15432', dbname 'data_node2') | - data_node3 | test_role_1 | timescaledb_fdw | | | | (host 'localhost', port '15432', dbname 'data_node3') | + List of foreign servers + Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | FDW options | Description +-------------+-------------+----------------------+-------------------+------+---------+--------------------------------------------------------+------------- + data_node_1 | test_role_1 | timescaledb_fdw | | | | (host 'localhost', port '15432', dbname 'data_node_1') | + data_node_2 | test_role_1 | timescaledb_fdw | | | | (host 'localhost', port '15432', dbname 'data_node_2') | + data_node_3 | test_role_1 | timescaledb_fdw | | | | (host 'localhost', port '15432', dbname 'data_node_3') | (3 rows) RESET ROLE; @@ -47,19 +51,19 @@ AS :TSL_MODULE_PATHNAME, 'tsl_invoke_faulty_distributed_command' LANGUAGE C STRICT; SET ROLE :ROLE_1; SELECT _timescaledb_internal.invoke_distributed_commands(); -INFO: data_node1 result: PGRES_COMMAND_OK -INFO: data_node2 result: PGRES_COMMAND_OK -INFO: data_node3 result: PGRES_COMMAND_OK -INFO: data_node1 result: PGRES_COMMAND_OK -INFO: data_node2 result: PGRES_COMMAND_OK -INFO: data_node1 result: PGRES_COMMAND_OK -INFO: data_node2 result: PGRES_COMMAND_OK +INFO: data_node_1 result: PGRES_COMMAND_OK +INFO: data_node_2 result: PGRES_COMMAND_OK +INFO: data_node_3 result: PGRES_COMMAND_OK +INFO: data_node_1 result: PGRES_COMMAND_OK +INFO: data_node_2 result: PGRES_COMMAND_OK +INFO: data_node_1 result: PGRES_COMMAND_OK +INFO: data_node_2 result: PGRES_COMMAND_OK invoke_distributed_commands ----------------------------- (1 row) -\c data_node1 +\c data_node_1 \dt List of relations Schema | Name | Type | Owner @@ -74,7 +78,7 @@ SELECT * FROM disttable1; Sat Sep 18 00:00:00 1976 PDT | 47 | 103.4 (1 row) -\c data_node2 +\c data_node_2 \dt List of relations Schema | Name | Type | Owner @@ -89,7 +93,7 @@ SELECT * FROM disttable1; Sat Sep 18 00:00:00 1976 PDT | 47 | 103.4 (1 row) -\c data_node3 +\c data_node_3 \dt List of relations Schema | Name | Type | Owner @@ -107,15 +111,15 @@ SET ROLE :ROLE_1; -- Verify failed insert command gets fully rolled back \set ON_ERROR_STOP 0 SELECT _timescaledb_internal.invoke_faulty_distributed_command(); -ERROR: [data_node3]: relation "public.disttable2" does not exist +ERROR: [data_node_3]: relation "public.disttable2" does not exist \set ON_ERROR_STOP 1 -\c data_node1 +\c data_node_1 SELECT * from disttable2; time | device | temp ------+--------+------ (0 rows) -\c data_node2 +\c data_node_2 SELECT * from disttable2; time | device | temp ------+--------+------ @@ -131,7 +135,7 @@ psql:include/remote_exec.sql:5: NOTICE: schema "test" already exists, skipping CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; -\c data_node1 +\c data_node_1 CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; @@ -141,7 +145,7 @@ SELECT is_frontend_session(); f (1 row) -\c data_node2 +\c data_node_2 CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; @@ -151,7 +155,7 @@ SELECT is_frontend_session(); f (1 row) -\c data_node3 +\c data_node_3 CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; @@ -171,31 +175,31 @@ SELECT is_frontend_session(); -- Ensure peer dist id is already set and can be set only once \set ON_ERROR_STOP 0 -SELECT * FROM test.remote_exec('{data_node1}', $$ SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec'); $$); -NOTICE: [data_node1]: SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec') -ERROR: [data_node1]: distributed peer ID already set +SELECT * FROM test.remote_exec('{data_node_1}', $$ SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec'); $$); +NOTICE: [data_node_1]: SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec') +ERROR: [data_node_1]: distributed peer ID already set \set ON_ERROR_STOP 1 -- Repeat is_frontend_session() test again, but this time using connections openned from frontend -- to backend nodes. Must return true. SELECT * FROM test.remote_exec(NULL, $$ SELECT is_frontend_session(); $$); -NOTICE: [data_node1]: SELECT is_frontend_session() -NOTICE: [data_node1]: +NOTICE: [data_node_1]: SELECT is_frontend_session() +NOTICE: [data_node_1]: is_frontend_session ------------------- t (1 row) -NOTICE: [data_node2]: SELECT is_frontend_session() -NOTICE: [data_node2]: +NOTICE: [data_node_2]: SELECT is_frontend_session() +NOTICE: [data_node_2]: is_frontend_session ------------------- t (1 row) -NOTICE: [data_node3]: SELECT is_frontend_session() -NOTICE: [data_node3]: +NOTICE: [data_node_3]: SELECT is_frontend_session() +NOTICE: [data_node_3]: is_frontend_session ------------------- t @@ -207,7 +211,120 @@ t (1 row) +-- Test distributed_exec() +-- Make sure dist session is properly set +SELECT * FROM distributed_exec('DO $$ BEGIN ASSERT(SELECT is_frontend_session()) = true; END; $$;'); + distributed_exec +------------------ + +(1 row) + +-- Test creating and dropping a table +SELECT * FROM distributed_exec('CREATE TABLE dist_test (id int)'); + distributed_exec +------------------ + +(1 row) + +SELECT * FROM distributed_exec('INSERT INTO dist_test values (7)'); + distributed_exec +------------------ + +(1 row) + +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from dist_test; $$); +NOTICE: [data_node_1]: SELECT * from dist_test +NOTICE: [data_node_1]: +id +-- + 7 +(1 row) + + +NOTICE: [data_node_2]: SELECT * from dist_test +NOTICE: [data_node_2]: +id +-- + 7 +(1 row) + + +NOTICE: [data_node_3]: SELECT * from dist_test +NOTICE: [data_node_3]: +id +-- + 7 +(1 row) + + + remote_exec +------------- + +(1 row) + +SELECT * FROM distributed_exec('DROP TABLE dist_test'); + distributed_exec +------------------ + +(1 row) + +\set ON_ERROR_STOP 0 +SELECT * FROM distributed_exec('INSERT INTO dist_test VALUES (8)', '{data_node_1}'); +ERROR: [data_node_1]: relation "dist_test" does not exist +\set ON_ERROR_STOP 1 +-- Test creating and dropping a role +CREATE ROLE dist_test_role; +-- Expect this to be an error, since data nodes are created on the same instance +\set ON_ERROR_STOP 0 +SELECT test.execute_sql_and_filter_data_node_name_on_error($$ +SELECT * FROM distributed_exec('CREATE ROLE dist_test_role'); +$$); +ERROR: [data_node_x]: role "dist_test_role" already exists +\set ON_ERROR_STOP 1 +SELECT * FROM test.remote_exec(NULL, $$ SELECT true from pg_catalog.pg_roles WHERE rolname = 'dist_test_role'; $$); +NOTICE: [data_node_1]: SELECT true from pg_catalog.pg_roles WHERE rolname = 'dist_test_role' +NOTICE: [data_node_1]: +bool +---- +t +(1 row) + + +NOTICE: [data_node_2]: SELECT true from pg_catalog.pg_roles WHERE rolname = 'dist_test_role' +NOTICE: [data_node_2]: +bool +---- +t +(1 row) + + +NOTICE: [data_node_3]: SELECT true from pg_catalog.pg_roles WHERE rolname = 'dist_test_role' +NOTICE: [data_node_3]: +bool +---- +t +(1 row) + + + remote_exec +------------- + +(1 row) + +DROP ROLE DIST_TEST_ROLE; +\set ON_ERROR_STOP 0 +SELECT test.execute_sql_and_filter_data_node_name_on_error($$ +SELECT * FROM distributed_exec('DROP ROLE dist_test_role'); +$$); +ERROR: [data_node_x]: role "dist_test_role" does not exist +\set ON_ERROR_STOP 1 +-- Do not allow to run distributed_exec() on a data nodes +\c data_node_1 +\set ON_ERROR_STOP 0 +SELECT * FROM distributed_exec('SELECT 1'); +ERROR: function must be run on the access node only +\set ON_ERROR_STOP 1 \c :TEST_DBNAME :ROLE_SUPERUSER -DROP DATABASE data_node1; -DROP DATABASE data_node2; -DROP DATABASE data_node3; +DROP DATABASE data_node_1; +DROP DATABASE data_node_2; +DROP DATABASE data_node_3; diff --git a/tsl/test/sql/dist_commands.sql b/tsl/test/sql/dist_commands.sql index c7f818805..bafa65a92 100644 --- a/tsl/test/sql/dist_commands.sql +++ b/tsl/test/sql/dist_commands.sql @@ -3,16 +3,25 @@ -- LICENSE-TIMESCALE for a copy of the license. \c :TEST_DBNAME :ROLE_SUPERUSER + +-- Support for execute_sql_and_filter_server_name_on_error() +\unset ECHO +\o /dev/null +\ir include/remote_exec.sql +\ir include/filter_exec.sql +\o +\set ECHO all + SET ROLE :ROLE_1; -SELECT * FROM add_data_node('data_node1', - database => 'data_node1', +SELECT * FROM add_data_node('data_node_1', + database => 'data_node_1', bootstrap_user => :'ROLE_CLUSTER_SUPERUSER'); -SELECT * FROM add_data_node('data_node2', - database => 'data_node2', +SELECT * FROM add_data_node('data_node_2', + database => 'data_node_2', bootstrap_user => :'ROLE_CLUSTER_SUPERUSER'); -SELECT * FROM add_data_node('data_node3', - database => 'data_node3', +SELECT * FROM add_data_node('data_node_3', + database => 'data_node_3', bootstrap_user => :'ROLE_CLUSTER_SUPERUSER'); \des+ @@ -31,13 +40,13 @@ SET ROLE :ROLE_1; SELECT _timescaledb_internal.invoke_distributed_commands(); -\c data_node1 +\c data_node_1 \dt SELECT * FROM disttable1; -\c data_node2 +\c data_node_2 \dt SELECT * FROM disttable1; -\c data_node3 +\c data_node_3 \dt SELECT * FROM disttable1; \c :TEST_DBNAME :ROLE_SUPERUSER @@ -48,9 +57,9 @@ SET ROLE :ROLE_1; SELECT _timescaledb_internal.invoke_faulty_distributed_command(); \set ON_ERROR_STOP 1 -\c data_node1 +\c data_node_1 SELECT * from disttable2; -\c data_node2 +\c data_node_2 SELECT * from disttable2; -- Test connection session identity @@ -68,19 +77,19 @@ CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; -\c data_node1 +\c data_node_1 CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; SELECT is_frontend_session(); -\c data_node2 +\c data_node_2 CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; SELECT is_frontend_session(); -\c data_node3 +\c data_node_3 CREATE OR REPLACE FUNCTION is_frontend_session() RETURNS BOOL AS :TSL_MODULE_PATHNAME, 'test_is_frontend_session' LANGUAGE C; @@ -92,14 +101,50 @@ SELECT is_frontend_session(); -- Ensure peer dist id is already set and can be set only once \set ON_ERROR_STOP 0 -SELECT * FROM test.remote_exec('{data_node1}', $$ SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec'); $$); +SELECT * FROM test.remote_exec('{data_node_1}', $$ SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec'); $$); \set ON_ERROR_STOP 1 -- Repeat is_frontend_session() test again, but this time using connections openned from frontend -- to backend nodes. Must return true. SELECT * FROM test.remote_exec(NULL, $$ SELECT is_frontend_session(); $$); +-- Test distributed_exec() + +-- Make sure dist session is properly set +SELECT * FROM distributed_exec('DO $$ BEGIN ASSERT(SELECT is_frontend_session()) = true; END; $$;'); + +-- Test creating and dropping a table +SELECT * FROM distributed_exec('CREATE TABLE dist_test (id int)'); +SELECT * FROM distributed_exec('INSERT INTO dist_test values (7)'); +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from dist_test; $$); +SELECT * FROM distributed_exec('DROP TABLE dist_test'); +\set ON_ERROR_STOP 0 +SELECT * FROM distributed_exec('INSERT INTO dist_test VALUES (8)', '{data_node_1}'); +\set ON_ERROR_STOP 1 + +-- Test creating and dropping a role +CREATE ROLE dist_test_role; +-- Expect this to be an error, since data nodes are created on the same instance +\set ON_ERROR_STOP 0 +SELECT test.execute_sql_and_filter_data_node_name_on_error($$ +SELECT * FROM distributed_exec('CREATE ROLE dist_test_role'); +$$); +\set ON_ERROR_STOP 1 +SELECT * FROM test.remote_exec(NULL, $$ SELECT true from pg_catalog.pg_roles WHERE rolname = 'dist_test_role'; $$); +DROP ROLE DIST_TEST_ROLE; +\set ON_ERROR_STOP 0 +SELECT test.execute_sql_and_filter_data_node_name_on_error($$ +SELECT * FROM distributed_exec('DROP ROLE dist_test_role'); +$$); +\set ON_ERROR_STOP 1 + +-- Do not allow to run distributed_exec() on a data nodes +\c data_node_1 +\set ON_ERROR_STOP 0 +SELECT * FROM distributed_exec('SELECT 1'); +\set ON_ERROR_STOP 1 + \c :TEST_DBNAME :ROLE_SUPERUSER -DROP DATABASE data_node1; -DROP DATABASE data_node2; -DROP DATABASE data_node3; +DROP DATABASE data_node_1; +DROP DATABASE data_node_2; +DROP DATABASE data_node_3;