Fix server detach/delete corner cases

Prevent server delete if the server contains data, unless user
specifies `force => true`. In case the server is the only data
replica, we don't allow delete/detach unless table/chunks are dropped.
The idea is to have the same semantics for delete as for detach since
delete actually calls detach

We also try to update pg_foreign_table when we delete server if there
is another server containing the same chunk.

An internal function is added to enable updating foreign table server
which might be useful in some cases since foreign table server is
considered a default server for that particular chunk.

Since this command needs to work even if the server we're trying to
remove is non responsive, we're not removing any data on the remote
data node.
This commit is contained in:
niksa 2019-06-04 21:17:01 +02:00 committed by Erik Nordström
parent b2fde83d2e
commit 0da34e840e
15 changed files with 608 additions and 139 deletions

View File

@ -178,7 +178,8 @@ AS '@MODULE_PATHNAME@', 'ts_server_add' LANGUAGE C VOLATILE;
CREATE OR REPLACE FUNCTION delete_server(
server_name NAME,
if_exists BOOLEAN = FALSE,
cascade BOOLEAN = FALSE
cascade BOOLEAN = FALSE,
force BOOLEAN = FALSE
) RETURNS BOOLEAN AS '@MODULE_PATHNAME@', 'ts_server_delete' LANGUAGE C VOLATILE;
-- Attach a server to a hypertable

View File

@ -5,3 +5,7 @@
-- Check if server is up
CREATE FUNCTION _timescaledb_internal.server_ping(server_name NAME) RETURNS BOOLEAN
AS '@MODULE_PATHNAME@', 'ts_server_ping' LANGUAGE C VOLATILE;
-- change default server for a chunk
CREATE OR REPLACE FUNCTION _timescaledb_internal.set_chunk_default_server(schema_name NAME, chunk_table_name NAME, server_name NAME) RETURNS BOOLEAN
AS '@MODULE_PATHNAME@', 'ts_set_chunk_default_server' LANGUAGE C VOLATILE;

View File

@ -120,9 +120,10 @@ extern List *ts_chunk_find_all_oids(Hypertable *ht, List *dimension_vecs, LOCKMO
extern TSDLLEXPORT int ts_chunk_add_constraints(Chunk *chunk);
extern Chunk *ts_chunk_copy(Chunk *chunk);
extern Chunk *ts_chunk_get_by_name_with_memory_context(const char *schema_name,
const char *table_name, MemoryContext mctx,
bool fail_if_not_found);
extern TSDLLEXPORT Chunk *ts_chunk_get_by_name_with_memory_context(const char *schema_name,
const char *table_name,
MemoryContext mctx,
bool fail_if_not_found);
extern TSDLLEXPORT void ts_chunk_insert_lock(Chunk *chunk, LOCKMODE lock);
extern TSDLLEXPORT Oid ts_chunk_create_table(Chunk *chunk, Hypertable *ht,

View File

@ -4,8 +4,14 @@
* LICENSE-APACHE for a copy of the license.
*/
#include <postgres.h>
#include <catalog/pg_foreign_table.h>
#include <catalog/pg_foreign_server.h>
#include <catalog/dependency.h>
#include <foreign/foreign.h>
#include <utils/builtins.h>
#include <utils/syscache.h>
#include <utils/inval.h>
#include <access/xact.h>
#include "chunk_server.h"
#include "scanner.h"
@ -223,6 +229,17 @@ ts_chunk_server_delete_by_chunk_id(int32 chunk_id)
CurrentMemoryContext);
}
int
ts_chunk_server_delete_by_chunk_id_and_server_name(int32 chunk_id, const char *server_name)
{
return ts_chunk_server_scan_by_chunk_id_and_server_internal(chunk_id,
server_name,
chunk_server_tuple_delete,
NULL,
RowExclusiveLock,
CurrentMemoryContext);
}
int
ts_chunk_server_delete_by_servername(const char *servername)
{
@ -250,9 +267,108 @@ ts_chunk_server_scan_by_servername_and_hypertable_id(const char *server_name, in
int32 chunk_id = lfirst_int(lc);
ChunkServer *cs =
ts_chunk_server_scan_by_chunk_id_and_servername(chunk_id, server_name, mctx);
results = lappend(results, cs);
if (cs != NULL)
results = lappend(results, cs);
}
MemoryContextSwitchTo(old);
return results;
}
bool
ts_chunk_server_contains_non_replicated_chunks(List *chunk_servers)
{
ListCell *lc;
foreach (lc, chunk_servers)
{
ChunkServer *cs = lfirst(lc);
List *replicas = ts_chunk_server_scan_by_chunk_id(cs->fd.chunk_id, CurrentMemoryContext);
if (list_length(replicas) < 2)
return true;
}
return false;
}
void
ts_chunk_server_update_foreign_table_server(Oid relid, Oid new_server_id)
{
Relation ftrel;
HeapTuple tuple;
HeapTuple copy;
Datum values[Natts_pg_foreign_table];
bool nulls[Natts_pg_foreign_table];
CatalogSecurityContext sec_ctx;
Oid old_server_id;
long updated;
tuple = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
errmsg("relation with OID %u does not exist", relid)));
ftrel = table_open(ForeignTableRelationId, RowExclusiveLock);
heap_deform_tuple(tuple, RelationGetDescr(ftrel), values, nulls);
old_server_id =
DatumGetObjectId(values[AttrNumberGetAttrOffset(Anum_pg_foreign_table_ftserver)]);
values[AttrNumberGetAttrOffset(Anum_pg_foreign_table_ftserver)] =
ObjectIdGetDatum(new_server_id);
copy = heap_form_tuple(RelationGetDescr(ftrel), values, nulls);
ts_catalog_database_info_become_owner(ts_catalog_database_info_get(), &sec_ctx);
ts_catalog_update_tid(ftrel, &tuple->t_self, copy);
ts_catalog_restore_user(&sec_ctx);
table_close(ftrel, RowExclusiveLock);
heap_freetuple(copy);
ReleaseSysCache(tuple);
/* invalidate foreign table cache */
CacheInvalidateRelcacheByRelid(ForeignTableRelationId);
/* update dependencies between foreign table and foreign server */
updated = changeDependencyFor(RelationRelationId,
relid,
ForeignServerRelationId,
old_server_id,
new_server_id);
if (updated != 1)
elog(ERROR,
"failed while trying to update server for foreign table %s",
get_rel_name(relid));
/* make changes visible */
CommandCounterIncrement();
}
void
ts_chunk_server_update_foreign_table_server_if_needed(int32 chunk_id, Oid existing_server_id)
{
ListCell *lc;
ChunkServer *new_server = NULL;
Chunk *chunk = ts_chunk_get_by_id(chunk_id, true);
ForeignTable *foreign_table = NULL;
Assert(chunk->relkind == RELKIND_FOREIGN_TABLE);
foreign_table = GetForeignTable(chunk->table_id);
/* no need to update since foreign table doesn't reference server we try to remove */
if (existing_server_id != foreign_table->serverid)
return;
Assert(list_length(chunk->servers) > 1);
foreach (lc, chunk->servers)
{
new_server = lfirst(lc);
if (new_server->foreign_server_oid != existing_server_id)
break;
}
Assert(new_server != NULL);
ts_chunk_server_update_foreign_table_server(chunk->table_id, new_server->foreign_server_oid);
}

View File

@ -22,9 +22,16 @@ ts_chunk_server_scan_by_chunk_id_and_servername(int32 chunk_id, const char *serv
extern TSDLLEXPORT void ts_chunk_server_insert(ChunkServer *server);
extern void ts_chunk_server_insert_multi(List *chunk_servers);
extern int ts_chunk_server_delete_by_chunk_id(int32 chunk_id);
extern TSDLLEXPORT int ts_chunk_server_delete_by_chunk_id_and_server_name(int32 chunk_id,
const char *server_name);
extern int ts_chunk_server_delete_by_servername(const char *servername);
extern TSDLLEXPORT List *
ts_chunk_server_scan_by_servername_and_hypertable_id(const char *server_name, int32 hypertable_id,
MemoryContext mctx);
extern TSDLLEXPORT bool ts_chunk_server_contains_non_replicated_chunks(List *chunk_servers);
extern TSDLLEXPORT void ts_chunk_server_update_foreign_table_server(Oid relid, Oid new_server_id);
extern TSDLLEXPORT void
ts_chunk_server_update_foreign_table_server_if_needed(int32 chunk_id, Oid existing_server_id);
#endif /* TIMESCALEDB_CHUNK_SERVER_H */

View File

@ -52,6 +52,7 @@ TS_FUNCTION_INFO_V1(ts_server_ping);
TS_FUNCTION_INFO_V1(ts_server_detach);
TS_FUNCTION_INFO_V1(ts_server_block_new_chunks);
TS_FUNCTION_INFO_V1(ts_server_allow_new_chunks);
TS_FUNCTION_INFO_V1(ts_set_chunk_default_server);
TS_FUNCTION_INFO_V1(ts_timescaledb_fdw_handler);
TS_FUNCTION_INFO_V1(ts_timescaledb_fdw_validator);
TS_FUNCTION_INFO_V1(ts_remote_txn_id_in);
@ -163,6 +164,12 @@ ts_server_allow_new_chunks(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(ts_cm_functions->server_set_block_new_chunks(fcinfo, false));
}
Datum
ts_set_chunk_default_server(PG_FUNCTION_ARGS)
{
PG_RETURN_DATUM(ts_cm_functions->set_chunk_default_server(fcinfo));
}
Datum
ts_timescaledb_fdw_handler(PG_FUNCTION_ARGS)
{
@ -596,6 +603,7 @@ TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = {
.server_ping = error_no_default_fn_pg_community,
.detach_server = error_no_default_fn_pg_community,
.server_set_block_new_chunks = server_set_block_new_chunks_default,
.set_chunk_default_server = error_no_default_fn_pg_community,
.show_chunk = error_no_default_fn_pg_community,
.create_chunk = error_no_default_fn_pg_community,
.create_chunk_on_servers = create_chunk_on_servers_default,

View File

@ -116,6 +116,7 @@ typedef struct CrossModuleFunctions
PGFunction server_ping;
PGFunction detach_server;
Datum (*server_set_block_new_chunks)(PG_FUNCTION_ARGS, bool block);
PGFunction set_chunk_default_server;
PGFunction create_chunk;
PGFunction show_chunk;
List *(*get_servername_list)(void);

View File

@ -54,3 +54,4 @@
#define ERRCODE_TS_UNEXPECTED MAKE_SQLSTATE('T', 'S', '5', '0', '1')
#define ERRCODE_TS_COMMUNICATION_ERROR MAKE_SQLSTATE('T', 'S', '5', '0', '2')
#define ERRCODE_TS_CHUNK_COLLISION MAKE_SQLSTATE('T', 'S', '5', '0', '3')
#define ERRCODE_TS_SERVER_IN_USE MAKE_SQLSTATE('T', 'S', '5', '0', '4')

View File

@ -2521,11 +2521,11 @@ ts_hypertable_get_servername_list(Hypertable *ht)
}
List *
ts_hypertable_get_available_servers(Hypertable *ht, bool error)
ts_hypertable_get_available_servers(Hypertable *ht, bool error_if_missing)
{
List *available_servers =
get_hypertable_server_values(ht, filter_non_blocked_servers, get_hypertable_server);
if (available_servers == NIL && error)
if (available_servers == NIL && error_if_missing)
ereport(ERROR,
(errcode(ERRCODE_TS_NO_SERVERS),
(errmsg("no available servers (detached or blocked for new chunks) for "

View File

@ -147,7 +147,7 @@ extern TSDLLEXPORT void ts_hypertable_clone_constraints_to_compressed(Hypertable
extern List *ts_hypertable_assign_chunk_servers(Hypertable *ht, Hypercube *cube);
extern TSDLLEXPORT List *ts_hypertable_get_servername_list(Hypertable *ht);
extern TSDLLEXPORT List *ts_hypertable_get_serverids_list(Hypertable *ht);
extern TSDLLEXPORT List *ts_hypertable_get_available_servers(Hypertable *ht, bool error);
extern TSDLLEXPORT List *ts_hypertable_get_available_servers(Hypertable *ht, bool error_if_missing);
extern TSDLLEXPORT List *ts_hypertable_get_available_server_oids(Hypertable *ht);
extern TSDLLEXPORT HypertableType ts_hypertable_get_type(Hypertable *ht);

View File

@ -229,6 +229,7 @@ CrossModuleFunctions tsl_cm_functions = {
.attach_server = error_not_supported_default_fn,
.detach_server = error_not_supported_default_fn,
.server_set_block_new_chunks = error_server_set_block_new_chunks_not_supported,
.set_chunk_default_server = error_not_supported_default_fn,
.show_chunk = error_not_supported_default_fn,
.create_chunk = error_not_supported_default_fn,
.create_chunk_on_servers = error_create_chunk_on_servers_not_supported,
@ -255,6 +256,7 @@ CrossModuleFunctions tsl_cm_functions = {
.server_ping = server_ping,
.detach_server = server_detach,
.server_set_block_new_chunks = server_set_block_new_chunks,
.set_chunk_default_server = server_set_chunk_default_server,
.show_chunk = chunk_show,
.create_chunk = chunk_create,
.create_chunk_on_servers = chunk_api_create_on_servers,

View File

@ -10,12 +10,15 @@
#include <nodes/makefuncs.h>
#include <nodes/parsenodes.h>
#include <catalog/pg_foreign_server.h>
#include <catalog/pg_foreign_table.h>
#include <catalog/namespace.h>
#include <catalog/pg_namespace.h>
#include <commands/dbcommands.h>
#include <commands/defrem.h>
#include <commands/event_trigger.h>
#include <utils/builtins.h>
#include <utils/inval.h>
#include <utils/syscache.h>
#include <libpq/crypt.h>
#include <miscadmin.h>
#include <funcapi.h>
@ -29,6 +32,7 @@
#if PG_VERSION_SUPPORTS_MULTINODE
#include "remote/async.h"
#include "remote/connection.h"
#include "remote/connection_cache.h"
#endif
#include "server.h"
#include "hypertable.h"
@ -607,77 +611,6 @@ server_add_without_dist_id(PG_FUNCTION_ARGS)
return server_add_internal(fcinfo, false);
}
Datum
server_delete(PG_FUNCTION_ARGS)
{
const char *servername = PG_ARGISNULL(0) ? NULL : PG_GETARG_CSTRING(0);
bool if_exists = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1);
bool cascade = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);
ForeignServer *server = GetForeignServerByName(servername, if_exists);
bool deleted = false;
if (NULL != server)
{
DropStmt stmt = {
.type = T_DropStmt,
#if PG96
.objects = list_make1(list_make1(makeString(pstrdup(servername)))),
#else
.objects = list_make1(makeString(pstrdup(servername))),
#endif
.removeType = OBJECT_FOREIGN_SERVER,
.behavior = cascade ? DROP_CASCADE : DROP_RESTRICT,
.missing_ok = if_exists,
};
ObjectAddress address = {
.classId = ForeignServerRelationId,
.objectId = server->serverid,
.objectSubId = 0,
};
ObjectAddress secondary_object = {
.classId = InvalidOid,
.objectId = InvalidOid,
.objectSubId = 0,
};
Node *parsetree = (Node *) &stmt;
/* Make sure event triggers are invoked so that all dropped objects
* are collected during a cascading drop. This ensures all dependent
* objects get cleaned up. */
EventTriggerBeginCompleteQuery();
#if !PG96
remove_distributed_id_from_backend(servername);
#endif
PG_TRY();
{
EventTriggerDDLCommandStart(parsetree);
RemoveObjects(&stmt);
EventTriggerCollectSimpleCommand(address, secondary_object, parsetree);
EventTriggerSQLDrop(parsetree);
EventTriggerDDLCommandEnd(parsetree);
}
PG_CATCH();
{
EventTriggerEndCompleteQuery();
PG_RE_THROW();
}
PG_END_TRY();
#if !PG96
/* Remove self from dist db if no longer have backends */
if (server_get_servername_list() == NIL)
dist_util_remove_from_db();
#endif
EventTriggerEndCompleteQuery();
deleted = true;
}
PG_RETURN_BOOL(deleted);
}
Datum
server_attach(PG_FUNCTION_ARGS)
{
@ -734,11 +667,36 @@ server_attach(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(create_hypertable_server_datum(fcinfo, (HypertableServer *) linitial(result)));
}
/* Only used for generating proper error message */
typedef enum OperationType
{
OP_BLOCK,
OP_DETACH,
OP_DELETE
} OperationType;
static char *
get_operation_type_message(OperationType op_type)
{
switch (op_type)
{
case OP_BLOCK:
return "blocking new chunks on";
case OP_DETACH:
return "detaching";
case OP_DELETE:
return "deleting";
default:
return NULL;
}
}
static void
check_replication(const char *server_name, Hypertable *ht, bool force, bool detach)
check_replication_for_new_data(const char *server_name, Hypertable *ht, bool force,
OperationType op_type)
{
List *available_servers = ts_hypertable_get_available_servers(ht, false);
char *operation = detach ? "detaching" : "blocking new chunks on";
char *operation = get_operation_type_message(op_type);
if (ht->fd.replication_factor < list_length(available_servers))
return;
@ -762,26 +720,57 @@ check_replication(const char *server_name, Hypertable *ht, bool force, bool deta
server_name)));
}
static void
server_detach_validate(const char *server_name, Hypertable *ht, bool force)
static List *
server_detach_validate(const char *server_name, Hypertable *ht, bool force, OperationType op_type)
{
List *chunk_servers =
ts_chunk_server_scan_by_servername_and_hypertable_id(server_name,
ht->fd.id,
CurrentMemoryContext);
bool has_non_replicated_chunks = ts_chunk_server_contains_non_replicated_chunks(chunk_servers);
char *operation = get_operation_type_message(op_type);
if (list_length(chunk_servers) > 0)
if (has_non_replicated_chunks)
ereport(ERROR,
(errcode(ERRCODE_TS_INTERNAL_ERROR),
errmsg("server \"%s\" cannot be detached because it contains chunks",
server_name)));
errmsg("%s server \"%s\" would mean a data-loss for hypertable "
"\"%s\" since server has the only data replica",
operation,
server_name,
NameStr(ht->fd.table_name)),
errhint("Ensure the server \"%s\" has no non-replicated data before %s it.",
server_name,
operation)));
check_replication(server_name, ht, force, true);
if (list_length(chunk_servers) > 0)
{
if (force)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("hypertable \"%s\" has under-replicated chunks due to %s "
"server \"%s\"",
NameStr(ht->fd.table_name),
operation,
server_name)));
else
ereport(ERROR,
(errcode(ERRCODE_TS_SERVER_IN_USE),
errmsg("%s server \"%s\" failed because it contains chunks "
"for hypertable \"%s\"",
operation,
server_name,
NameStr(ht->fd.table_name))));
}
check_replication_for_new_data(server_name, ht, force, op_type);
return chunk_servers;
}
static int
server_modify_hypertable_servers(const char *server_name, List *hypertable_servers,
bool all_hypertables, bool detach, bool block_chunks, bool force)
bool all_hypertables, OperationType op_type, bool block_chunks,
bool force)
{
Cache *hcache = ts_hypertable_cache_pin();
ListCell *lc;
@ -806,10 +795,24 @@ server_modify_hypertable_servers(const char *server_name, List *hypertable_serve
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied for hypertable \"%s\"", get_rel_name(relid))));
else if (detach)
else if (op_type == OP_DETACH || op_type == OP_DELETE)
{
/* we have permissions to detach */
server_detach_validate(NameStr(server->fd.server_name), ht, force);
List *chunk_servers =
server_detach_validate(NameStr(server->fd.server_name), ht, force, op_type);
ListCell *cs_lc;
/* update chunk foreign table server and delete chunk mapping */
foreach (cs_lc, chunk_servers)
{
ChunkServer *cs = lfirst(cs_lc);
ts_chunk_server_update_foreign_table_server_if_needed(cs->fd.chunk_id,
cs->foreign_server_oid);
ts_chunk_server_delete_by_chunk_id_and_server_name(cs->fd.chunk_id,
NameStr(cs->fd.server_name));
}
/* delete hypertable mapping */
removed +=
ts_hypertable_server_delete_by_servername_and_hypertable_id(server_name, ht->fd.id);
}
@ -829,7 +832,7 @@ server_modify_hypertable_servers(const char *server_name, List *hypertable_serve
continue;
}
check_replication(server_name, ht, force, false);
check_replication_for_new_data(server_name, ht, force, OP_BLOCK);
}
server->fd.block_chunks = block_chunks;
removed += ts_hypertable_server_update(server);
@ -846,19 +849,19 @@ server_block_hypertable_servers(const char *server_name, List *hypertable_server
return server_modify_hypertable_servers(server_name,
hypertable_servers,
all_hypertables,
false,
OP_BLOCK,
block_chunks,
force);
}
static int
server_detach_hypertable_servers(const char *server_name, List *hypertable_servers,
bool all_hypertables, bool force)
bool all_hypertables, bool force, OperationType op_type)
{
return server_modify_hypertable_servers(server_name,
hypertable_servers,
all_hypertables,
true,
op_type,
false,
force);
}
@ -951,11 +954,111 @@ server_detach(PG_FUNCTION_ARGS)
hypertable_servers =
ts_hypertable_server_scan_by_server_name(server_name, CurrentMemoryContext);
removed =
server_detach_hypertable_servers(server_name, hypertable_servers, all_hypertables, force);
removed = server_detach_hypertable_servers(server_name,
hypertable_servers,
all_hypertables,
force,
OP_DETACH);
PG_RETURN_INT32(removed);
}
Datum
server_delete(PG_FUNCTION_ARGS)
{
const char *server_name = PG_ARGISNULL(0) ? NULL : PG_GETARG_CSTRING(0);
bool if_exists = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1);
bool cascade = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);
bool force = PG_ARGISNULL(3) ? false : PG_GETARG_BOOL(3);
ForeignServer *server = GetForeignServerByName(server_name, if_exists);
List *hypertable_servers = NIL;
DropStmt stmt;
ObjectAddress address;
ObjectAddress secondary_object = {
.classId = InvalidOid,
.objectId = InvalidOid,
.objectSubId = 0,
};
Node *parsetree = NULL;
#if !PG96
UserMapping *um = NULL;
Cache *conn_cache;
#endif
if (server_name == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid server_name: cannot be NULL")));
if (server == NULL)
PG_RETURN_BOOL(false);
#if !PG96
um = get_user_mapping(GetUserId(), server->serverid);
if (um != NULL)
{
conn_cache = remote_connection_cache_pin();
remote_connection_cache_remove(conn_cache, um);
ts_cache_release(conn_cache);
}
#endif
/* detach server */
hypertable_servers =
ts_hypertable_server_scan_by_server_name(server_name, CurrentMemoryContext);
server_detach_hypertable_servers(server_name, hypertable_servers, true, force, OP_DELETE);
stmt = (DropStmt)
{
.type = T_DropStmt,
#if PG96
.objects = list_make1(list_make1(makeString(pstrdup(server_name)))),
#else
.objects = list_make1(makeString(pstrdup(server_name))),
#endif
.removeType = OBJECT_FOREIGN_SERVER, .behavior = cascade ? DROP_CASCADE : DROP_RESTRICT,
.missing_ok = if_exists,
};
parsetree = (Node *) &stmt;
/* Make sure event triggers are invoked so that all dropped objects
* are collected during a cascading drop. This ensures all dependent
* objects get cleaned up. */
EventTriggerBeginCompleteQuery();
#if !PG96
remove_distributed_id_from_backend(server_name);
#endif
PG_TRY();
{
ObjectAddressSet(address, ForeignServerRelationId, server->serverid);
EventTriggerDDLCommandStart(parsetree);
RemoveObjects(&stmt);
EventTriggerCollectSimpleCommand(address, secondary_object, parsetree);
EventTriggerSQLDrop(parsetree);
EventTriggerDDLCommandEnd(parsetree);
}
PG_CATCH();
{
EventTriggerEndCompleteQuery();
PG_RE_THROW();
}
PG_END_TRY();
#if !PG96
/* Remove self from dist db if no longer have backends */
if (server_get_servername_list() == NIL)
dist_util_remove_from_db();
#endif
EventTriggerEndCompleteQuery();
CommandCounterIncrement();
CacheInvalidateRelcacheByRelid(ForeignServerRelationId);
PG_RETURN_BOOL(true);
}
List *
server_get_servername_list(void)
{
@ -1042,3 +1145,16 @@ server_ping(PG_FUNCTION_ARGS)
(errmsg("server ping is only supported on PG10 and above"))));
#endif
}
Datum
server_set_chunk_default_server(PG_FUNCTION_ARGS)
{
char *schema_name = PG_ARGISNULL(0) ? NULL : PG_GETARG_CSTRING(0);
char *table_name = PG_ARGISNULL(1) ? NULL : PG_GETARG_CSTRING(1);
char *server_name = PG_ARGISNULL(2) ? NULL : PG_GETARG_CSTRING(2);
ForeignServer *server = GetForeignServerByName(server_name, false);
Chunk *chunk = chunk_get_by_name(schema_name, table_name, true);
ts_chunk_server_update_foreign_table_server(chunk->table_id, server->serverid);
PG_RETURN_BOOL(true);
}

View File

@ -15,6 +15,7 @@ extern Datum server_detach(PG_FUNCTION_ARGS);
extern Datum server_set_block_new_chunks(PG_FUNCTION_ARGS, bool block);
extern List *server_get_servername_list(void);
extern Datum server_ping(PG_FUNCTION_ARGS);
extern Datum server_set_chunk_default_server(PG_FUNCTION_ARGS);
/* This should only be used for testing */
extern Datum server_add_without_dist_id(PG_FUNCTION_ARGS);

View File

@ -389,6 +389,15 @@ SELECT * FROM test.show_subtables('disttable');
_timescaledb_internal._hyper_3_4_dist_chunk |
(2 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
-----------------------+---------------------
_hyper_3_3_dist_chunk | server_4
_hyper_3_4_dist_chunk | server_2
(2 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped
----+---------------+-----------------------+-----------------------+---------------------+---------
@ -405,14 +414,51 @@ SELECT * FROM _timescaledb_catalog.chunk_server;
4 | 3 | server_4
(4 rows)
SELECT * FROM _timescaledb_internal.set_chunk_default_server('_timescaledb_internal', '_hyper_3_3_dist_chunk', 'server_2');
set_chunk_default_server
--------------------------
t
(1 row)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
-----------------------+---------------------
_hyper_3_3_dist_chunk | server_2
_hyper_3_4_dist_chunk | server_2
(2 rows)
SELECT * FROM _timescaledb_internal.set_chunk_default_server('_timescaledb_internal', '_hyper_3_3_dist_chunk', 'server_4');
set_chunk_default_server
--------------------------
t
(1 row)
\set ON_ERROR_STOP 0
-- Will fail because server_2 contains chunks
SELECT * FROM delete_server('server_2', cascade => true);
ERROR: deleting server "server_2" failed because it contains chunks for hypertable "disttable"
-- non-existing chunk
SELECT * FROM _timescaledb_internal.set_chunk_default_server('x', 'x_chunk', 'server_4');
ERROR: chunk not found
-- non-existing server
SELECT * FROM _timescaledb_internal.set_chunk_default_server('_timescaledb_internal', '_hyper_3_3_dist_chunk', 'server_0000');
ERROR: server "server_0000" does not exist
-- NULL try
SELECT * FROM _timescaledb_internal.set_chunk_default_server(NULL, NULL, 'server_4');
ERROR: chunk not found
\set ON_ERROR_STOP 1
-- Deleting a server removes the "foreign" chunk table(s) that
-- reference that server as "primary" and should also remove the
-- hypertable_server and chunk_server mappings for that server. In
-- the future we might want to fallback to a replica server for those
-- chunks that have multiple servers so that the chunk is not removed
-- unnecessarily.
SELECT * FROM delete_server('server_2', cascade => true);
NOTICE: drop cascades to 2 other objects
-- unnecessarily. We use force => true b/c server_2 contains chunks.
SELECT * FROM delete_server('server_2', cascade => true, force => true);
WARNING: hypertable "disttable" has under-replicated chunks due to deleting server "server_2"
WARNING: new data for hypertable "disttable" will be under-replicated due to deleting server "server_2"
NOTICE: drop cascades to user mapping for super_user on server server_2
delete_server
---------------
t
@ -422,13 +468,24 @@ SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
---------------------------------------------+------------
_timescaledb_internal._hyper_3_3_dist_chunk |
(1 row)
_timescaledb_internal._hyper_3_4_dist_chunk |
(2 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
-----------------------+---------------------
_hyper_3_3_dist_chunk | server_4
_hyper_3_4_dist_chunk | server_4
(2 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped
----+---------------+-----------------------+-----------------------+---------------------+---------
3 | 3 | _timescaledb_internal | _hyper_3_3_dist_chunk | | f
(1 row)
4 | 3 | _timescaledb_internal | _hyper_3_4_dist_chunk | | f
(2 rows)
SELECT * FROM _timescaledb_catalog.hypertable_server;
hypertable_id | server_hypertable_id | server_name | block_chunks
@ -440,11 +497,17 @@ SELECT * FROM _timescaledb_catalog.chunk_server;
chunk_id | server_chunk_id | server_name
----------+-----------------+-------------
3 | 2 | server_4
(1 row)
4 | 3 | server_4
(2 rows)
\set ON_ERROR_STOP 0
-- can't delete b/c it's last data replica
SELECT * FROM delete_server('server_4', cascade => true, force => true);
ERROR: deleting server "server_4" would mean a data-loss for hypertable "disttable" since server has the only data replica
\set ON_ERROR_STOP 1
-- Should also clean up hypertable_server when using standard DDL commands
DROP SERVER server_4 CASCADE;
NOTICE: drop cascades to 2 other objects
NOTICE: drop cascades to 3 other objects
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
-------+------------
@ -665,7 +728,7 @@ SELECT * FROM _timescaledb_catalog.chunk_server;
7 | 2 | server_1
(6 rows)
-- Add additional hypertable
-- Add additional hypertable
CREATE TABLE disttable_2(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable_2', 'time', replication_factor => 2, servers => '{"server_1", "server_2", "server_3"}');
NOTICE: adding not-null constraint to column "time"
@ -712,7 +775,7 @@ SELECT * FROM _timescaledb_catalog.hypertable_server;
7 | 2 | server_1 | t
(6 rows)
-- insert more data
-- insert more data
INSERT INTO disttable VALUES
('2019-08-02 10:45', 1, 14.4),
('2019-08-15 10:45', 4, 14.9),
@ -844,7 +907,7 @@ SELECT * FROM detach_server(NULL, 'disttable');
ERROR: invalid server_name: cannot be NULL
-- Can't detach server_1 b/c it contains data for disttable
SELECT * FROM detach_server('server_1');
ERROR: server "server_1" cannot be detached because it contains chunks
ERROR: detaching server "server_1" failed because it contains chunks for hypertable "disttable"
-- can't detach already detached server
SELECT * FROM detach_server('server_2', 'disttable_2');
ERROR: server "server_2" is not attached to hypertable "disttable_2"
@ -863,6 +926,93 @@ WARNING: new data for hypertable "disttable_2" will be under-replicated due to
1
(1 row)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
------------------------+---------------------
_hyper_6_10_dist_chunk | server_3
_hyper_6_5_dist_chunk | server_1
_hyper_6_6_dist_chunk | server_2
_hyper_6_7_dist_chunk | server_3
_hyper_6_8_dist_chunk | server_3
_hyper_6_9_dist_chunk | server_2
(6 rows)
-- force detach server with data
SELECT * FROM detach_server('server_3', 'disttable', true);
WARNING: hypertable "disttable" has under-replicated chunks due to detaching server "server_3"
detach_server
---------------
1
(1 row)
-- chunk and hypertable metadata should be deleted as well
SELECT * FROM _timescaledb_catalog.chunk_server;
chunk_id | server_chunk_id | server_name
----------+-----------------+-------------
5 | 1 | server_1
5 | 1 | server_2
6 | 2 | server_2
7 | 2 | server_1
8 | 3 | server_2
9 | 4 | server_2
10 | 5 | server_2
(7 rows)
SELECT * FROM _timescaledb_catalog.hypertable_server;
hypertable_id | server_hypertable_id | server_name | block_chunks
---------------+----------------------+-------------+--------------
6 | 1 | server_1 | f
7 | 2 | server_1 | f
6 | 1 | server_2 | f
(3 rows)
-- detached server_3 should not show up any more
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
------------------------+---------------------
_hyper_6_10_dist_chunk | server_2
_hyper_6_5_dist_chunk | server_1
_hyper_6_6_dist_chunk | server_2
_hyper_6_7_dist_chunk | server_1
_hyper_6_8_dist_chunk | server_2
_hyper_6_9_dist_chunk | server_2
(6 rows)
\set ON_ERROR_STOP 0
-- detaching server with last data replica should ERROR even when forcing
SELECT * FROM detach_server('server_2', 'disttable', true);
ERROR: detaching server "server_2" would mean a data-loss for hypertable "disttable" since server has the only data replica
\set ON_ERROR_STOP 1
-- drop all chunks
SELECT * FROM drop_chunks(table_name => 'disttable', older_than => '2200-01-01 00:00'::timestamptz);
drop_chunks
----------------------------------------------
_timescaledb_internal._hyper_6_5_dist_chunk
_timescaledb_internal._hyper_6_6_dist_chunk
_timescaledb_internal._hyper_6_7_dist_chunk
_timescaledb_internal._hyper_6_8_dist_chunk
_timescaledb_internal._hyper_6_9_dist_chunk
_timescaledb_internal._hyper_6_10_dist_chunk
(6 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
--------------------+---------------------
(0 rows)
SELECT * FROM detach_server('server_2', 'disttable', true);
WARNING: new data for hypertable "disttable" will be under-replicated due to detaching server "server_2"
detach_server
---------------
1
(1 row)
-- Need explicit password for non-super users to connect
ALTER ROLE :ROLE_DEFAULT_CLUSTER_USER CREATEDB PASSWORD 'pass';
-- Let's add more servers
@ -916,35 +1066,39 @@ NOTICE: skipping hypertable "disttable_3" due to missing permissions
-- Cleanup
RESET ROLE;
SELECT * FROM delete_server('server_1', cascade => true);
SELECT * FROM delete_server('server_1', cascade => true, force =>true);
WARNING: new data for hypertable "disttable" will be under-replicated due to deleting server "server_1"
WARNING: new data for hypertable "disttable_2" will be under-replicated due to deleting server "server_1"
NOTICE: drop cascades to user mapping for super_user on server server_1
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_2', cascade => true, force =>true);
NOTICE: drop cascades to user mapping for super_user on server server_2
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_3', cascade => true, force =>true);
NOTICE: drop cascades to user mapping for super_user on server server_3
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_4', cascade => true, force =>true);
NOTICE: drop cascades to 2 other objects
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_2', cascade => true);
NOTICE: drop cascades to 3 other objects
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_3', cascade => true);
NOTICE: drop cascades to 4 other objects
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_4', cascade => true);
NOTICE: drop cascades to 2 other objects
delete_server
---------------
t
(1 row)
SELECT * FROM delete_server('server_5', cascade => true);
SELECT * FROM delete_server('server_5', cascade => true, force =>true);
WARNING: new data for hypertable "disttable_3" will be under-replicated due to deleting server "server_5"
WARNING: new data for hypertable "disttable_4" will be under-replicated due to deleting server "server_5"
NOTICE: drop cascades to 2 other objects
delete_server
---------------

View File

@ -194,22 +194,52 @@ SELECT * FROM _timescaledb_catalog.chunk_server;
SELECT * FROM drop_chunks(older_than => '2019-05-22 17:18'::timestamptz);
SELECT * FROM test.show_subtables('disttable');
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
SELECT * FROM _timescaledb_catalog.chunk;
SELECT * FROM _timescaledb_catalog.chunk_server;
SELECT * FROM _timescaledb_internal.set_chunk_default_server('_timescaledb_internal', '_hyper_3_3_dist_chunk', 'server_2');
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
SELECT * FROM _timescaledb_internal.set_chunk_default_server('_timescaledb_internal', '_hyper_3_3_dist_chunk', 'server_4');
\set ON_ERROR_STOP 0
-- Will fail because server_2 contains chunks
SELECT * FROM delete_server('server_2', cascade => true);
-- non-existing chunk
SELECT * FROM _timescaledb_internal.set_chunk_default_server('x', 'x_chunk', 'server_4');
-- non-existing server
SELECT * FROM _timescaledb_internal.set_chunk_default_server('_timescaledb_internal', '_hyper_3_3_dist_chunk', 'server_0000');
-- NULL try
SELECT * FROM _timescaledb_internal.set_chunk_default_server(NULL, NULL, 'server_4');
\set ON_ERROR_STOP 1
-- Deleting a server removes the "foreign" chunk table(s) that
-- reference that server as "primary" and should also remove the
-- hypertable_server and chunk_server mappings for that server. In
-- the future we might want to fallback to a replica server for those
-- chunks that have multiple servers so that the chunk is not removed
-- unnecessarily.
SELECT * FROM delete_server('server_2', cascade => true);
-- unnecessarily. We use force => true b/c server_2 contains chunks.
SELECT * FROM delete_server('server_2', cascade => true, force => true);
SELECT * FROM test.show_subtables('disttable');
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
SELECT * FROM _timescaledb_catalog.chunk;
SELECT * FROM _timescaledb_catalog.hypertable_server;
SELECT * FROM _timescaledb_catalog.chunk_server;
\set ON_ERROR_STOP 0
-- can't delete b/c it's last data replica
SELECT * FROM delete_server('server_4', cascade => true, force => true);
\set ON_ERROR_STOP 1
-- Should also clean up hypertable_server when using standard DDL commands
DROP SERVER server_4 CASCADE;
@ -314,7 +344,7 @@ SELECT * FROM _timescaledb_catalog.chunk;
SELECT * FROM _timescaledb_catalog.hypertable_server;
SELECT * FROM _timescaledb_catalog.chunk_server;
-- Add additional hypertable
-- Add additional hypertable
CREATE TABLE disttable_2(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable_2', 'time', replication_factor => 2, servers => '{"server_1", "server_2", "server_3"}');
@ -331,7 +361,7 @@ SELECT * FROM block_new_chunks_on_server('server_1');
SELECT * FROM _timescaledb_catalog.hypertable_server;
-- insert more data
-- insert more data
INSERT INTO disttable VALUES
('2019-08-02 10:45', 1, 14.4),
('2019-08-15 10:45', 4, 14.9),
@ -396,6 +426,33 @@ SELECT * FROM detach_server('server_3', 'devices');
-- force detach server to become under-replicated for new data
SELECT * FROM detach_server('server_3', 'disttable_2', true);
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
-- force detach server with data
SELECT * FROM detach_server('server_3', 'disttable', true);
-- chunk and hypertable metadata should be deleted as well
SELECT * FROM _timescaledb_catalog.chunk_server;
SELECT * FROM _timescaledb_catalog.hypertable_server;
-- detached server_3 should not show up any more
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
\set ON_ERROR_STOP 0
-- detaching server with last data replica should ERROR even when forcing
SELECT * FROM detach_server('server_2', 'disttable', true);
\set ON_ERROR_STOP 1
-- drop all chunks
SELECT * FROM drop_chunks(table_name => 'disttable', older_than => '2200-01-01 00:00'::timestamptz);
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
SELECT * FROM detach_server('server_2', 'disttable', true);
-- Need explicit password for non-super users to connect
ALTER ROLE :ROLE_DEFAULT_CLUSTER_USER CREATEDB PASSWORD 'pass';
@ -425,13 +482,13 @@ SELECT * FROM detach_server('server_4');
-- Cleanup
RESET ROLE;
SELECT * FROM delete_server('server_1', cascade => true);
SELECT * FROM delete_server('server_2', cascade => true);
SELECT * FROM delete_server('server_3', cascade => true);
SELECT * FROM delete_server('server_4', cascade => true);
SELECT * FROM delete_server('server_5', cascade => true);
SELECT * FROM delete_server('server_1', cascade => true, force =>true);
SELECT * FROM delete_server('server_2', cascade => true, force =>true);
SELECT * FROM delete_server('server_3', cascade => true, force =>true);
SELECT * FROM delete_server('server_4', cascade => true, force =>true);
SELECT * FROM delete_server('server_5', cascade => true, force =>true);
DROP DATABASE server_1;
DROP DATABASE server_2;
DROP DATABASE server_3;
DROP DATABASE server_4;
DROP DATABASE server_5;
DROP DATABASE server_5;