mirror of https://github.com/citusdata/citus.git
Adds create / drop database propagation support (#7240)
DESCRIPTION: Adds support for propagating `CREATE`/`DROP` database In this PR, create and drop database support is added. For CREATE DATABASE: * "oid" option is not supported * specifying "strategy" to be different than "wal_log" is not supported * specifying "template" to be different than "template1" is not supported The last two are because those are not saved in `pg_database` and when activating a node, we cannot assume what parameters were provided when creating the database. And "oid" is not supported because whether user specified an arbitrary oid when creating the database is not saved in pg_database and we want to avoid from oid collisions that might arise from attempting to use an auto-assigned oid on workers. Finally, in case of node activation, GRANTs for the database are also propagated. --------- Co-authored-by: Jelte Fennema-Nio <github-tech@jeltef.nl> Co-authored-by: Jelte Fennema-Nio <jelte.fennema@microsoft.com> Co-authored-by: Onur Tirtir <onurcantirtir@gmail.com>pull/7133/merge
parent
cedcc220bf
commit
3b556cb5ed
|
@ -10,34 +10,76 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
#include "miscadmin.h"
|
||||||
|
|
||||||
|
#include "access/heapam.h"
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/objectaddress.h"
|
#include "catalog/objectaddress.h"
|
||||||
|
#include "catalog/pg_collation.h"
|
||||||
#include "catalog/pg_database.h"
|
#include "catalog/pg_database.h"
|
||||||
|
#include "catalog/pg_database_d.h"
|
||||||
|
#include "catalog/pg_tablespace.h"
|
||||||
#include "commands/dbcommands.h"
|
#include "commands/dbcommands.h"
|
||||||
#include "miscadmin.h"
|
#include "commands/defrem.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/lsyscache.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
|
#include "utils/relcache.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
#include "distributed/adaptive_executor.h"
|
||||||
#include "distributed/commands.h"
|
#include "distributed/commands.h"
|
||||||
#include "distributed/commands/utility_hook.h"
|
#include "distributed/commands/utility_hook.h"
|
||||||
|
#include "distributed/deparse_shard_query.h"
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
|
#include "distributed/metadata/distobject.h"
|
||||||
#include "distributed/metadata_sync.h"
|
#include "distributed/metadata_sync.h"
|
||||||
#include "distributed/metadata_utility.h"
|
#include "distributed/metadata_utility.h"
|
||||||
#include "distributed/multi_executor.h"
|
#include "distributed/multi_executor.h"
|
||||||
#include "distributed/relation_access_tracking.h"
|
#include "distributed/relation_access_tracking.h"
|
||||||
|
#include "distributed/worker_protocol.h"
|
||||||
#include "distributed/worker_transaction.h"
|
#include "distributed/worker_transaction.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DatabaseCollationInfo is used to store collation related information of a database.
|
||||||
|
*/
|
||||||
|
typedef struct DatabaseCollationInfo
|
||||||
|
{
|
||||||
|
char *datcollate;
|
||||||
|
char *datctype;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
|
char *daticulocale;
|
||||||
|
char *datcollversion;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
char *daticurules;
|
||||||
|
#endif
|
||||||
|
} DatabaseCollationInfo;
|
||||||
|
|
||||||
|
static char * GenerateCreateDatabaseStatementFromPgDatabase(Form_pg_database
|
||||||
|
databaseForm);
|
||||||
|
static DatabaseCollationInfo GetDatabaseCollation(Oid dbOid);
|
||||||
static AlterOwnerStmt * RecreateAlterDatabaseOwnerStmt(Oid databaseOid);
|
static AlterOwnerStmt * RecreateAlterDatabaseOwnerStmt(Oid databaseOid);
|
||||||
static Oid get_database_owner(Oid db_oid);
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
List * PreprocessGrantOnDatabaseStmt(Node *node, const char *queryString,
|
static char * GetLocaleProviderString(char datlocprovider);
|
||||||
ProcessUtilityContext processUtilityContext);
|
#endif
|
||||||
|
static char * GetTablespaceName(Oid tablespaceOid);
|
||||||
|
static ObjectAddress * GetDatabaseAddressFromDatabaseName(char *databaseName,
|
||||||
|
bool missingOk);
|
||||||
|
|
||||||
|
static Oid get_database_owner(Oid dbId);
|
||||||
|
|
||||||
|
|
||||||
/* controlled via GUC */
|
/* controlled via GUC */
|
||||||
|
bool EnableCreateDatabasePropagation = false;
|
||||||
bool EnableAlterDatabaseOwner = true;
|
bool EnableAlterDatabaseOwner = true;
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* AlterDatabaseOwnerObjectAddress returns the ObjectAddress of the database that is the
|
* AlterDatabaseOwnerObjectAddress returns the ObjectAddress of the database that is the
|
||||||
* object of the AlterOwnerStmt. Errors if missing_ok is false.
|
* object of the AlterOwnerStmt. Errors if missing_ok is false.
|
||||||
|
@ -94,13 +136,13 @@ RecreateAlterDatabaseOwnerStmt(Oid databaseOid)
|
||||||
* get_database_owner returns the Oid of the role owning the database
|
* get_database_owner returns the Oid of the role owning the database
|
||||||
*/
|
*/
|
||||||
static Oid
|
static Oid
|
||||||
get_database_owner(Oid db_oid)
|
get_database_owner(Oid dbId)
|
||||||
{
|
{
|
||||||
HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(db_oid));
|
HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbId));
|
||||||
if (!HeapTupleIsValid(tuple))
|
if (!HeapTupleIsValid(tuple))
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||||
errmsg("database with OID %u does not exist", db_oid)));
|
errmsg("database with OID %u does not exist", dbId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Oid dba = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
|
Oid dba = ((Form_pg_database) GETSTRUCT(tuple))->datdba;
|
||||||
|
@ -213,7 +255,6 @@ PreprocessAlterDatabaseRefreshCollStmt(Node *node, const char *queryString,
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PreprocessAlterDatabaseSetStmt is executed before the statement is applied to the local
|
* PreprocessAlterDatabaseSetStmt is executed before the statement is applied to the local
|
||||||
* postgres instance.
|
* postgres instance.
|
||||||
|
@ -242,3 +283,420 @@ PreprocessAlterDatabaseSetStmt(Node *node, const char *queryString,
|
||||||
|
|
||||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessAlterDatabaseStmt is executed before the statement is applied to the local
|
||||||
|
* Postgres instance.
|
||||||
|
*
|
||||||
|
* In this stage, we perform validations that we want to ensure before delegating to
|
||||||
|
* previous utility hooks because it might not be convenient to throw an error in an
|
||||||
|
* implicit transaction that creates a database.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessCreateDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
if (!EnableCreateDatabasePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
EnsureSupportedCreateDatabaseCommand(stmt);
|
||||||
|
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PostprocessCreateDatabaseStmt is executed after the statement is applied to the local
|
||||||
|
* postgres instance. In this stage we prepare the commands that need to be run on
|
||||||
|
* all workers to create the database. Since the CREATE DATABASE statement gives error
|
||||||
|
* in a transaction block, we need to use NontransactionalNodeDDLTaskList to send the
|
||||||
|
* CREATE DATABASE statement to the workers.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PostprocessCreateDatabaseStmt(Node *node, const char *queryString)
|
||||||
|
{
|
||||||
|
if (!EnableCreateDatabasePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given that CREATE DATABASE doesn't support "IF NOT EXISTS" and we're
|
||||||
|
* in the post-process, database must exist, hence missingOk = false.
|
||||||
|
*/
|
||||||
|
bool missingOk = false;
|
||||||
|
bool isPostProcess = true;
|
||||||
|
List *addresses = GetObjectAddressListFromParseTree(node, missingOk,
|
||||||
|
isPostProcess);
|
||||||
|
EnsureAllObjectDependenciesExistOnAllNodes(addresses);
|
||||||
|
|
||||||
|
char *createDatabaseCommand = DeparseTreeNode(node);
|
||||||
|
|
||||||
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) createDatabaseCommand,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
return NontransactionalNodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PreprocessDropDatabaseStmt is executed after the statement is applied to the local
|
||||||
|
* postgres instance. In this stage we can prepare the commands that need to be run on
|
||||||
|
* all workers to drop the database. Since the DROP DATABASE statement gives error in
|
||||||
|
* transaction context, we need to use NontransactionalNodeDDLTaskList to send the
|
||||||
|
* DROP DATABASE statement to the workers.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
PreprocessDropDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext)
|
||||||
|
{
|
||||||
|
if (!EnableCreateDatabasePropagation || !ShouldPropagate())
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCoordinator();
|
||||||
|
|
||||||
|
DropdbStmt *stmt = (DropdbStmt *) node;
|
||||||
|
|
||||||
|
bool isPostProcess = false;
|
||||||
|
List *addresses = GetObjectAddressListFromParseTree(node, stmt->missing_ok,
|
||||||
|
isPostProcess);
|
||||||
|
|
||||||
|
if (list_length(addresses) != 1)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("unexpected number of objects found when "
|
||||||
|
"executing DROP DATABASE command")));
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectAddress *address = (ObjectAddress *) linitial(addresses);
|
||||||
|
if (address->objectId == InvalidOid || !IsAnyObjectDistributed(list_make1(address)))
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *dropDatabaseCommand = DeparseTreeNode(node);
|
||||||
|
|
||||||
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||||
|
(void *) dropDatabaseCommand,
|
||||||
|
ENABLE_DDL_PROPAGATION);
|
||||||
|
|
||||||
|
return NontransactionalNodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DropDatabaseStmtObjectAddress gets the ObjectAddress of the database that is the
|
||||||
|
* object of the DropdbStmt.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
DropDatabaseStmtObjectAddress(Node *node, bool missingOk, bool isPostprocess)
|
||||||
|
{
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
return list_make1(dbAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateDatabaseStmtObjectAddress gets the ObjectAddress of the database that is the
|
||||||
|
* object of the CreatedbStmt.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
CreateDatabaseStmtObjectAddress(Node *node, bool missingOk, bool isPostprocess)
|
||||||
|
{
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
ObjectAddress *dbAddress = GetDatabaseAddressFromDatabaseName(stmt->dbname,
|
||||||
|
missingOk);
|
||||||
|
return list_make1(dbAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EnsureSupportedCreateDatabaseCommand validates the options provided for the CREATE
|
||||||
|
* DATABASE command.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* stmt: A CreatedbStmt struct representing a CREATE DATABASE command.
|
||||||
|
* The options field is a list of DefElem structs, each representing an option.
|
||||||
|
*
|
||||||
|
* Currently, this function checks for the following:
|
||||||
|
* - The "oid" option is not supported.
|
||||||
|
* - The "template" option is only supported with the value "template1".
|
||||||
|
* - The "strategy" option is only supported with the value "wal_log".
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
EnsureSupportedCreateDatabaseCommand(CreatedbStmt *stmt)
|
||||||
|
{
|
||||||
|
DefElem *option = NULL;
|
||||||
|
foreach_ptr(option, stmt->options)
|
||||||
|
{
|
||||||
|
if (strcmp(option->defname, "oid") == 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
errmsg("CREATE DATABASE option \"%s\" is not supported",
|
||||||
|
option->defname));
|
||||||
|
}
|
||||||
|
|
||||||
|
char *optionValue = defGetString(option);
|
||||||
|
|
||||||
|
if (strcmp(option->defname, "template") == 0 &&
|
||||||
|
strcmp(optionValue, "template1") != 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR, errmsg("Only template1 is supported as template "
|
||||||
|
"parameter for CREATE DATABASE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(option->defname, "strategy") == 0 &&
|
||||||
|
strcmp(optionValue, "wal_log") != 0)
|
||||||
|
{
|
||||||
|
ereport(ERROR, errmsg("Only wal_log is supported as strategy "
|
||||||
|
"parameter for CREATE DATABASE"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetDatabaseAddressFromDatabaseName gets the database name and returns the ObjectAddress
|
||||||
|
* of the database.
|
||||||
|
*/
|
||||||
|
static ObjectAddress *
|
||||||
|
GetDatabaseAddressFromDatabaseName(char *databaseName, bool missingOk)
|
||||||
|
{
|
||||||
|
Oid databaseOid = get_database_oid(databaseName, missingOk);
|
||||||
|
ObjectAddress *dbObjectAddress = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*dbObjectAddress, DatabaseRelationId, databaseOid);
|
||||||
|
return dbObjectAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetTablespaceName gets the tablespace oid and returns the tablespace name.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
GetTablespaceName(Oid tablespaceOid)
|
||||||
|
{
|
||||||
|
HeapTuple tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tablespaceOid));
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_tablespace tablespaceForm = (Form_pg_tablespace) GETSTRUCT(tuple);
|
||||||
|
char *tablespaceName = pstrdup(NameStr(tablespaceForm->spcname));
|
||||||
|
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
|
return tablespaceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetDatabaseCollation gets oid of a database and returns all the collation related information
|
||||||
|
* We need this method since collation related info in Form_pg_database is not accessible.
|
||||||
|
*/
|
||||||
|
static DatabaseCollationInfo
|
||||||
|
GetDatabaseCollation(Oid dbOid)
|
||||||
|
{
|
||||||
|
DatabaseCollationInfo info;
|
||||||
|
memset(&info, 0, sizeof(DatabaseCollationInfo));
|
||||||
|
|
||||||
|
Relation rel = table_open(DatabaseRelationId, AccessShareLock);
|
||||||
|
HeapTuple tup = get_catalog_object_by_oid(rel, Anum_pg_database_oid, dbOid);
|
||||||
|
if (!HeapTupleIsValid(tup))
|
||||||
|
{
|
||||||
|
elog(ERROR, "cache lookup failed for database %u", dbOid);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNull = false;
|
||||||
|
|
||||||
|
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||||
|
|
||||||
|
Datum collationDatum = heap_getattr(tup, Anum_pg_database_datcollate, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
info.datcollate = TextDatumGetCString(collationDatum);
|
||||||
|
|
||||||
|
Datum ctypeDatum = heap_getattr(tup, Anum_pg_database_datctype, tupdesc, &isNull);
|
||||||
|
info.datctype = TextDatumGetCString(ctypeDatum);
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
|
|
||||||
|
Datum icuLocaleDatum = heap_getattr(tup, Anum_pg_database_daticulocale, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
info.daticulocale = TextDatumGetCString(icuLocaleDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
Datum collverDatum = heap_getattr(tup, Anum_pg_database_datcollversion, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
info.datcollversion = TextDatumGetCString(collverDatum);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
Datum icurulesDatum = heap_getattr(tup, Anum_pg_database_daticurules, tupdesc,
|
||||||
|
&isNull);
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
info.daticurules = TextDatumGetCString(icurulesDatum);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
table_close(rel, AccessShareLock);
|
||||||
|
heap_freetuple(tup);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetLocaleProviderString gets the datlocprovider stored in pg_database
|
||||||
|
* and returns the string representation of the datlocprovider
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
GetLocaleProviderString(char datlocprovider)
|
||||||
|
{
|
||||||
|
switch (datlocprovider)
|
||||||
|
{
|
||||||
|
case 'c':
|
||||||
|
{
|
||||||
|
return "libc";
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
{
|
||||||
|
return "icu";
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("unexpected datlocprovider value: %c",
|
||||||
|
datlocprovider)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GenerateCreateDatabaseStatementFromPgDatabase gets the pg_database tuple and returns the
|
||||||
|
* CREATE DATABASE statement that can be used to create given database.
|
||||||
|
*
|
||||||
|
* Note that this doesn't deparse OID of the database and this is not a
|
||||||
|
* problem as we anyway don't allow specifying custom OIDs for databases
|
||||||
|
* when creating them.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
GenerateCreateDatabaseStatementFromPgDatabase(Form_pg_database databaseForm)
|
||||||
|
{
|
||||||
|
DatabaseCollationInfo collInfo = GetDatabaseCollation(databaseForm->oid);
|
||||||
|
|
||||||
|
StringInfoData str;
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
appendStringInfo(&str, "CREATE DATABASE %s",
|
||||||
|
quote_identifier(NameStr(databaseForm->datname)));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " CONNECTION LIMIT %d", databaseForm->datconnlimit);
|
||||||
|
|
||||||
|
appendStringInfo(&str, " ALLOW_CONNECTIONS = %s",
|
||||||
|
quote_literal_cstr(databaseForm->datallowconn ? "true" : "false"));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " IS_TEMPLATE = %s",
|
||||||
|
quote_literal_cstr(databaseForm->datistemplate ? "true" : "false"));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " LC_COLLATE = %s",
|
||||||
|
quote_literal_cstr(collInfo.datcollate));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " LC_CTYPE = %s", quote_literal_cstr(collInfo.datctype));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " OWNER = %s",
|
||||||
|
quote_identifier(GetUserNameFromId(databaseForm->datdba, false)));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " TABLESPACE = %s",
|
||||||
|
quote_identifier(GetTablespaceName(databaseForm->dattablespace)));
|
||||||
|
|
||||||
|
appendStringInfo(&str, " ENCODING = %s",
|
||||||
|
quote_literal_cstr(pg_encoding_to_char(databaseForm->encoding)));
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
|
if (collInfo.datcollversion != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, " COLLATION_VERSION = %s",
|
||||||
|
quote_identifier(collInfo.datcollversion));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collInfo.daticulocale != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, " ICU_LOCALE = %s", quote_identifier(
|
||||||
|
collInfo.daticulocale));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(&str, " LOCALE_PROVIDER = %s",
|
||||||
|
quote_identifier(GetLocaleProviderString(
|
||||||
|
databaseForm->datlocprovider)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_16
|
||||||
|
if (collInfo.daticurules != NULL)
|
||||||
|
{
|
||||||
|
appendStringInfo(&str, " ICU_RULES = %s", quote_identifier(
|
||||||
|
collInfo.daticurules));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateDatabaseDDLCommand returns a CREATE DATABASE command to create given
|
||||||
|
* database
|
||||||
|
*
|
||||||
|
* Command is wrapped by citus_internal_database_command() UDF
|
||||||
|
* to avoid from transaction block restrictions that apply to database commands.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
CreateDatabaseDDLCommand(Oid dbId)
|
||||||
|
{
|
||||||
|
HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbId));
|
||||||
|
if (!HeapTupleIsValid(tuple))
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||||
|
errmsg("database with OID %u does not exist", dbId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_database databaseForm = (Form_pg_database) GETSTRUCT(tuple);
|
||||||
|
|
||||||
|
char *createStmt = GenerateCreateDatabaseStatementFromPgDatabase(databaseForm);
|
||||||
|
|
||||||
|
StringInfo outerDbStmt = makeStringInfo();
|
||||||
|
|
||||||
|
/* Generate the CREATE DATABASE statement */
|
||||||
|
appendStringInfo(outerDbStmt,
|
||||||
|
"SELECT pg_catalog.citus_internal_database_command(%s)",
|
||||||
|
quote_literal_cstr(createStmt));
|
||||||
|
|
||||||
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
|
return outerDbStmt->data;
|
||||||
|
}
|
||||||
|
|
|
@ -457,16 +457,37 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
||||||
|
|
||||||
case OCLASS_DATABASE:
|
case OCLASS_DATABASE:
|
||||||
{
|
{
|
||||||
List *databaseDDLCommands = NIL;
|
/*
|
||||||
|
* For the database where Citus is installed, only propagate the ownership of the
|
||||||
/* only propagate the ownership of the database when the feature is on */
|
* database, only when the feature is on.
|
||||||
if (EnableAlterDatabaseOwner)
|
*
|
||||||
|
* This is because this database must exist on all nodes already so we shouldn't
|
||||||
|
* need to "CREATE" it on other nodes. However, we still need to correctly reflect
|
||||||
|
* its owner on other nodes too.
|
||||||
|
*/
|
||||||
|
if (dependency->objectId == MyDatabaseId && EnableAlterDatabaseOwner)
|
||||||
{
|
{
|
||||||
List *ownerDDLCommands = DatabaseOwnerDDLCommands(dependency);
|
return DatabaseOwnerDDLCommands(dependency);
|
||||||
databaseDDLCommands = list_concat(databaseDDLCommands, ownerDDLCommands);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return databaseDDLCommands;
|
/*
|
||||||
|
* For the other databases, create the database on all nodes, only when the feature
|
||||||
|
* is on.
|
||||||
|
*/
|
||||||
|
if (dependency->objectId != MyDatabaseId && EnableCreateDatabasePropagation)
|
||||||
|
{
|
||||||
|
char *databaseDDLCommand = CreateDatabaseDDLCommand(dependency->objectId);
|
||||||
|
|
||||||
|
List *ddlCommands = list_make1(databaseDDLCommand);
|
||||||
|
|
||||||
|
List *grantDDLCommands = GrantOnDatabaseDDLCommands(dependency->objectId);
|
||||||
|
|
||||||
|
ddlCommands = list_concat(ddlCommands, grantDDLCommands);
|
||||||
|
|
||||||
|
return ddlCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
case OCLASS_PROC:
|
case OCLASS_PROC:
|
||||||
|
|
|
@ -475,6 +475,28 @@ static DistributeObjectOps Database_Alter = {
|
||||||
.markDistributed = false,
|
.markDistributed = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Create = {
|
||||||
|
.deparse = DeparseCreateDatabaseStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessCreateDatabaseStmt,
|
||||||
|
.postprocess = PostprocessCreateDatabaseStmt,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_CREATE,
|
||||||
|
.address = CreateDatabaseStmtObjectAddress,
|
||||||
|
.markDistributed = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DistributeObjectOps Database_Drop = {
|
||||||
|
.deparse = DeparseDropDatabaseStmt,
|
||||||
|
.qualify = NULL,
|
||||||
|
.preprocess = PreprocessDropDatabaseStmt,
|
||||||
|
.postprocess = NULL,
|
||||||
|
.objectType = OBJECT_DATABASE,
|
||||||
|
.operationType = DIST_OPS_DROP,
|
||||||
|
.address = DropDatabaseStmtObjectAddress,
|
||||||
|
.markDistributed = false,
|
||||||
|
};
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
static DistributeObjectOps Database_RefreshColl = {
|
static DistributeObjectOps Database_RefreshColl = {
|
||||||
.deparse = DeparseAlterDatabaseRefreshCollStmt,
|
.deparse = DeparseAlterDatabaseRefreshCollStmt,
|
||||||
|
@ -1343,6 +1365,16 @@ GetDistributeObjectOps(Node *node)
|
||||||
return &Database_Alter;
|
return &Database_Alter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case T_CreatedbStmt:
|
||||||
|
{
|
||||||
|
return &Database_Create;
|
||||||
|
}
|
||||||
|
|
||||||
|
case T_DropdbStmt:
|
||||||
|
{
|
||||||
|
return &Database_Drop;
|
||||||
|
}
|
||||||
|
|
||||||
#if PG_VERSION_NUM >= PG_VERSION_15
|
#if PG_VERSION_NUM >= PG_VERSION_15
|
||||||
case T_AlterDatabaseRefreshCollStmt:
|
case T_AlterDatabaseRefreshCollStmt:
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "access/htup_details.h"
|
#include "access/htup_details.h"
|
||||||
#include "catalog/catalog.h"
|
#include "catalog/catalog.h"
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
#include "citus_version.h"
|
#include "citus_version.h"
|
||||||
#include "commands/dbcommands.h"
|
#include "commands/dbcommands.h"
|
||||||
#include "commands/defrem.h"
|
#include "commands/defrem.h"
|
||||||
|
@ -694,7 +695,7 @@ citus_ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* inform the user about potential caveats */
|
/* inform the user about potential caveats */
|
||||||
if (IsA(parsetree, CreatedbStmt))
|
if (IsA(parsetree, CreatedbStmt) && !EnableCreateDatabasePropagation)
|
||||||
{
|
{
|
||||||
if (EnableUnsupportedFeatureMessages)
|
if (EnableUnsupportedFeatureMessages)
|
||||||
{
|
{
|
||||||
|
@ -724,22 +725,13 @@ citus_ProcessUtilityInternal(PlannedStmt *pstmt,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Make sure that dropping the role deletes the pg_dist_object entries. There is a
|
* Make sure that dropping node-wide objects deletes the pg_dist_object
|
||||||
* separate logic for roles, since roles are not included as dropped objects in the
|
* entries. There is a separate logic for node-wide objects (such as role
|
||||||
* drop event trigger. To handle it both on worker and coordinator nodes, it is not
|
* and databases), since they are not included as dropped objects in the
|
||||||
* implemented as a part of process functions but here.
|
* drop event trigger. To handle it both on worker and coordinator nodes,
|
||||||
|
* it is not implemented as a part of process functions but here.
|
||||||
*/
|
*/
|
||||||
if (IsA(parsetree, DropRoleStmt))
|
UnmarkNodeWideObjectsDistributed(parsetree);
|
||||||
{
|
|
||||||
DropRoleStmt *stmt = castNode(DropRoleStmt, parsetree);
|
|
||||||
List *allDropRoles = stmt->roles;
|
|
||||||
|
|
||||||
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
|
|
||||||
if (list_length(distributedDropRoles) > 0)
|
|
||||||
{
|
|
||||||
UnmarkRolesDistributed(distributedDropRoles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pstmt->utilityStmt = parsetree;
|
pstmt->utilityStmt = parsetree;
|
||||||
|
|
||||||
|
@ -1275,9 +1267,12 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob)
|
||||||
{
|
{
|
||||||
ereport(WARNING,
|
ereport(WARNING,
|
||||||
(errmsg(
|
(errmsg(
|
||||||
"CONCURRENTLY-enabled index commands can fail partially, "
|
"Commands that are not transaction-safe may result in "
|
||||||
"leaving behind an INVALID index.\n Use DROP INDEX "
|
"partial failure, potentially leading to an inconsistent "
|
||||||
"CONCURRENTLY IF EXISTS to remove the invalid index.")));
|
"state.\nIf the problematic command is a CREATE operation, "
|
||||||
|
"consider using the 'IF EXISTS' syntax to drop the object,"
|
||||||
|
"\nif applicable, and then re-attempt the original command.")));
|
||||||
|
|
||||||
PG_RE_THROW();
|
PG_RE_THROW();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1491,6 +1486,28 @@ DDLTaskList(Oid relationId, const char *commandString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NontransactionalNodeDDLTaskList builds a list of tasks to execute a DDL command on a
|
||||||
|
* given target set of nodes with cannotBeExecutedInTransaction is set to make sure
|
||||||
|
* that task list is executed outside a transaction block.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
NontransactionalNodeDDLTaskList(TargetWorkerSet targets, List *commands)
|
||||||
|
{
|
||||||
|
List *ddlJobs = NodeDDLTaskList(targets, commands);
|
||||||
|
DDLJob *ddlJob = NULL;
|
||||||
|
foreach_ptr(ddlJob, ddlJobs)
|
||||||
|
{
|
||||||
|
Task *task = NULL;
|
||||||
|
foreach_ptr(task, ddlJob->taskList)
|
||||||
|
{
|
||||||
|
task->cannotBeExecutedInTransaction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ddlJobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NodeDDLTaskList builds a list of tasks to execute a DDL command on a
|
* NodeDDLTaskList builds a list of tasks to execute a DDL command on a
|
||||||
* given target set of nodes.
|
* given target set of nodes.
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* citus_deparseutils.c
|
||||||
|
*
|
||||||
|
* This file contains common functions used for deparsing PostgreSQL
|
||||||
|
* statements to their equivalent SQL representation.
|
||||||
|
*
|
||||||
|
* Copyright (c) Citus Data, Inc.
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pg_version_constants.h"
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
|
||||||
|
#include "commands/defrem.h"
|
||||||
|
#include "distributed/deparser.h"
|
||||||
|
#include "utils/builtins.h"
|
||||||
|
#include "utils/elog.h"
|
||||||
|
#include "utils/rel.h"
|
||||||
|
#include "utils/relcache.h"
|
||||||
|
#include "utils/syscache.h"
|
||||||
|
#include "utils/typcache.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefElemOptionToStatement converts a DefElem option to a SQL statement and
|
||||||
|
* appends it to the given StringInfo buffer.
|
||||||
|
*
|
||||||
|
* @param buf The StringInfo buffer to append the SQL statement to.
|
||||||
|
* @param option The DefElem option to convert to a SQL statement.
|
||||||
|
* @param optionFormats The option format specification to use for the conversion.
|
||||||
|
* @param optionFormatsLen The number of option formats in the opt_formats array.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
DefElemOptionToStatement(StringInfo buf, DefElem *option,
|
||||||
|
const DefElemOptionFormat *optionFormats,
|
||||||
|
int optionFormatsLen)
|
||||||
|
{
|
||||||
|
const char *name = option->defname;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < optionFormatsLen; i++)
|
||||||
|
{
|
||||||
|
if (strcmp(name, optionFormats[i].name) == 0)
|
||||||
|
{
|
||||||
|
switch (optionFormats[i].type)
|
||||||
|
{
|
||||||
|
case OPTION_FORMAT_STRING:
|
||||||
|
{
|
||||||
|
char *value = defGetString(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, quote_identifier(
|
||||||
|
value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_FORMAT_INTEGER:
|
||||||
|
{
|
||||||
|
int32 value = defGetInt32(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_FORMAT_BOOLEAN:
|
||||||
|
{
|
||||||
|
bool value = defGetBoolean(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, value ? "true" :
|
||||||
|
"false");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPTION_FORMAT_LITERAL_CSTR:
|
||||||
|
{
|
||||||
|
char *value = defGetString(option);
|
||||||
|
appendStringInfo(buf, optionFormats[i].format, quote_literal_cstr(
|
||||||
|
value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
elog(ERROR, "unrecognized option type: %d", optionFormats[i].type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,23 +12,46 @@
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "pg_version_compat.h"
|
#include "pg_version_compat.h"
|
||||||
|
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
|
#include "commands/defrem.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "nodes/parsenodes.h"
|
#include "nodes/parsenodes.h"
|
||||||
|
#include "parser/parse_type.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/commands.h"
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
#include "commands/defrem.h"
|
|
||||||
#include "distributed/deparser.h"
|
#include "distributed/deparser.h"
|
||||||
|
#include "distributed/listutils.h"
|
||||||
#include "distributed/log_utils.h"
|
#include "distributed/log_utils.h"
|
||||||
#include "parser/parse_type.h"
|
|
||||||
|
|
||||||
|
|
||||||
static void AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
static void AppendAlterDatabaseOwnerStmt(StringInfo buf, AlterOwnerStmt *stmt);
|
||||||
|
static void AppendAlterDatabaseSetStmt(StringInfo buf, AlterDatabaseSetStmt *stmt);
|
||||||
static void AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt);
|
static void AppendAlterDatabaseStmt(StringInfo buf, AlterDatabaseStmt *stmt);
|
||||||
static void AppendDefElemConnLimit(StringInfo buf, DefElem *def);
|
static void AppendDefElemConnLimit(StringInfo buf, DefElem *def);
|
||||||
|
static void AppendCreateDatabaseStmt(StringInfo buf, CreatedbStmt *stmt);
|
||||||
|
static void AppendDropDatabaseStmt(StringInfo buf, DropdbStmt *stmt);
|
||||||
|
static void AppendGrantOnDatabaseStmt(StringInfo buf, GrantStmt *stmt);
|
||||||
|
|
||||||
|
const DefElemOptionFormat create_database_option_formats[] = {
|
||||||
|
{ "owner", " OWNER %s", OPTION_FORMAT_STRING },
|
||||||
|
{ "template", " TEMPLATE %s", OPTION_FORMAT_STRING },
|
||||||
|
{ "encoding", " ENCODING %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "strategy", " STRATEGY %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "locale", " LOCALE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "lc_collate", " LC_COLLATE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "lc_ctype", " LC_CTYPE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "icu_locale", " ICU_LOCALE %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "icu_rules", " ICU_RULES %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "locale_provider", " LOCALE_PROVIDER %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "collation_version", " COLLATION_VERSION %s", OPTION_FORMAT_LITERAL_CSTR },
|
||||||
|
{ "tablespace", " TABLESPACE %s", OPTION_FORMAT_STRING },
|
||||||
|
{ "allow_connections", " ALLOW_CONNECTIONS %s", OPTION_FORMAT_BOOLEAN },
|
||||||
|
{ "connection_limit", " CONNECTION LIMIT %d", OPTION_FORMAT_INTEGER },
|
||||||
|
{ "is_template", " IS_TEMPLATE %s", OPTION_FORMAT_BOOLEAN }
|
||||||
|
};
|
||||||
|
|
||||||
char *
|
char *
|
||||||
DeparseAlterDatabaseOwnerStmt(Node *node)
|
DeparseAlterDatabaseOwnerStmt(Node *node)
|
||||||
|
@ -205,3 +228,87 @@ DeparseAlterDatabaseSetStmt(Node *node)
|
||||||
|
|
||||||
return str.data;
|
return str.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendCreateDatabaseStmt(StringInfo buf, CreatedbStmt *stmt)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Make sure that we don't try to deparse something that this
|
||||||
|
* function doesn't expect.
|
||||||
|
*/
|
||||||
|
EnsureSupportedCreateDatabaseCommand(stmt);
|
||||||
|
|
||||||
|
appendStringInfo(buf,
|
||||||
|
"CREATE DATABASE %s",
|
||||||
|
quote_identifier(stmt->dbname));
|
||||||
|
|
||||||
|
DefElem *option = NULL;
|
||||||
|
foreach_ptr(option, stmt->options)
|
||||||
|
{
|
||||||
|
DefElemOptionToStatement(buf, option, create_database_option_formats,
|
||||||
|
lengthof(create_database_option_formats));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseCreateDatabaseStmt(Node *node)
|
||||||
|
{
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, node);
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
AppendCreateDatabaseStmt(&str, stmt);
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
AppendDropDatabaseStmt(StringInfo buf, DropdbStmt *stmt)
|
||||||
|
{
|
||||||
|
char *ifExistsStatement = stmt->missing_ok ? "IF EXISTS" : "";
|
||||||
|
appendStringInfo(buf,
|
||||||
|
"DROP DATABASE %s %s",
|
||||||
|
ifExistsStatement,
|
||||||
|
quote_identifier(stmt->dbname));
|
||||||
|
|
||||||
|
if (list_length(stmt->options) > 1)
|
||||||
|
{
|
||||||
|
/* FORCE is the only option that can be provided for this command */
|
||||||
|
elog(ERROR, "got unexpected number of options for DROP DATABASE");
|
||||||
|
}
|
||||||
|
else if (list_length(stmt->options) == 1)
|
||||||
|
{
|
||||||
|
DefElem *option = linitial(stmt->options);
|
||||||
|
appendStringInfo(buf, " WITH ( ");
|
||||||
|
|
||||||
|
if (strcmp(option->defname, "force") == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(buf, "FORCE");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* FORCE is the only option that can be provided for this command */
|
||||||
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
|
errmsg("unrecognized DROP DATABASE option \"%s\"",
|
||||||
|
option->defname)));
|
||||||
|
}
|
||||||
|
|
||||||
|
appendStringInfo(buf, " )");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char *
|
||||||
|
DeparseDropDatabaseStmt(Node *node)
|
||||||
|
{
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
StringInfoData str = { 0 };
|
||||||
|
initStringInfo(&str);
|
||||||
|
|
||||||
|
AppendDropDatabaseStmt(&str, stmt);
|
||||||
|
|
||||||
|
return str.data;
|
||||||
|
}
|
||||||
|
|
|
@ -698,7 +698,6 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
||||||
|
|
||||||
case OCLASS_DATABASE:
|
case OCLASS_DATABASE:
|
||||||
{
|
{
|
||||||
/* only to propagate its owner */
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,13 @@
|
||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/objectaddress.h"
|
#include "catalog/objectaddress.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
#include "catalog/pg_extension_d.h"
|
#include "catalog/pg_extension_d.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "citus_version.h"
|
#include "citus_version.h"
|
||||||
|
#include "commands/dbcommands.h"
|
||||||
#include "commands/extension.h"
|
#include "commands/extension.h"
|
||||||
#include "distributed/listutils.h"
|
#include "distributed/listutils.h"
|
||||||
#include "distributed/colocation_utils.h"
|
#include "distributed/colocation_utils.h"
|
||||||
|
@ -49,7 +51,6 @@
|
||||||
#include "utils/regproc.h"
|
#include "utils/regproc.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
|
|
||||||
|
|
||||||
static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress);
|
static char * CreatePgDistObjectEntryCommand(const ObjectAddress *objectAddress);
|
||||||
static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
|
static int ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
|
||||||
Datum *paramValues);
|
Datum *paramValues);
|
||||||
|
@ -357,6 +358,42 @@ ExecuteCommandAsSuperuser(char *query, int paramCount, Oid *paramTypes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UnmarkNodeWideObjectsDistributed deletes pg_dist_object records
|
||||||
|
* for all distributed objects in given Drop stmt node.
|
||||||
|
*
|
||||||
|
* Today we only expect DropRoleStmt and DropdbStmt to get here.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
UnmarkNodeWideObjectsDistributed(Node *node)
|
||||||
|
{
|
||||||
|
if (IsA(node, DropRoleStmt))
|
||||||
|
{
|
||||||
|
DropRoleStmt *stmt = castNode(DropRoleStmt, node);
|
||||||
|
List *allDropRoles = stmt->roles;
|
||||||
|
|
||||||
|
List *distributedDropRoles = FilterDistributedRoles(allDropRoles);
|
||||||
|
if (list_length(distributedDropRoles) > 0)
|
||||||
|
{
|
||||||
|
UnmarkRolesDistributed(distributedDropRoles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (IsA(node, DropdbStmt))
|
||||||
|
{
|
||||||
|
DropdbStmt *stmt = castNode(DropdbStmt, node);
|
||||||
|
char *dbName = stmt->dbname;
|
||||||
|
|
||||||
|
Oid dbOid = get_database_oid(dbName, stmt->missing_ok);
|
||||||
|
ObjectAddress *dbObjectAddress = palloc0(sizeof(ObjectAddress));
|
||||||
|
ObjectAddressSet(*dbObjectAddress, DatabaseRelationId, dbOid);
|
||||||
|
if (IsAnyObjectDistributed(list_make1(dbObjectAddress)))
|
||||||
|
{
|
||||||
|
UnmarkObjectDistributed(dbObjectAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UnmarkObjectDistributed removes the entry from pg_dist_object that marks this object as
|
* UnmarkObjectDistributed removes the entry from pg_dist_object that marks this object as
|
||||||
* distributed. This will prevent updates to that object to be propagated to the worker.
|
* distributed. This will prevent updates to that object to be propagated to the worker.
|
||||||
|
|
|
@ -30,12 +30,15 @@
|
||||||
#include "catalog/pg_attrdef.h"
|
#include "catalog/pg_attrdef.h"
|
||||||
#include "catalog/pg_collation.h"
|
#include "catalog/pg_collation.h"
|
||||||
#include "catalog/pg_constraint.h"
|
#include "catalog/pg_constraint.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
|
#include "catalog/pg_database_d.h"
|
||||||
#include "catalog/pg_depend.h"
|
#include "catalog/pg_depend.h"
|
||||||
#include "catalog/pg_foreign_server.h"
|
#include "catalog/pg_foreign_server.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/async.h"
|
#include "commands/async.h"
|
||||||
|
#include "commands/dbcommands.h"
|
||||||
#include "distributed/argutils.h"
|
#include "distributed/argutils.h"
|
||||||
#include "distributed/backend_data.h"
|
#include "distributed/backend_data.h"
|
||||||
#include "distributed/citus_ruleutils.h"
|
#include "distributed/citus_ruleutils.h"
|
||||||
|
@ -120,6 +123,7 @@ static List * GetObjectsForGrantStmt(ObjectType objectType, Oid objectId);
|
||||||
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
|
static AccessPriv * GetAccessPrivObjectForGrantStmt(char *permission);
|
||||||
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
|
static List * GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid,
|
||||||
AclItem *aclItem);
|
AclItem *aclItem);
|
||||||
|
static List * GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem);
|
||||||
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
|
static List * GenerateGrantOnFunctionQueriesFromAclItem(Oid schemaOid,
|
||||||
AclItem *aclItem);
|
AclItem *aclItem);
|
||||||
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
|
static List * GrantOnSequenceDDLCommands(Oid sequenceOid);
|
||||||
|
@ -179,6 +183,7 @@ PG_FUNCTION_INFO_V1(citus_internal_delete_colocation_metadata);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_add_tenant_schema);
|
PG_FUNCTION_INFO_V1(citus_internal_add_tenant_schema);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_delete_tenant_schema);
|
PG_FUNCTION_INFO_V1(citus_internal_delete_tenant_schema);
|
||||||
PG_FUNCTION_INFO_V1(citus_internal_update_none_dist_table_metadata);
|
PG_FUNCTION_INFO_V1(citus_internal_update_none_dist_table_metadata);
|
||||||
|
PG_FUNCTION_INFO_V1(citus_internal_database_command);
|
||||||
|
|
||||||
|
|
||||||
static bool got_SIGTERM = false;
|
static bool got_SIGTERM = false;
|
||||||
|
@ -2043,6 +2048,92 @@ GenerateGrantOnSchemaQueriesFromAclItem(Oid schemaOid, AclItem *aclItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GrantOnDatabaseDDLCommands creates a list of ddl command for replicating the permissions
|
||||||
|
* of roles on databases.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
GrantOnDatabaseDDLCommands(Oid databaseOid)
|
||||||
|
{
|
||||||
|
HeapTuple databaseTuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(databaseOid));
|
||||||
|
bool isNull = true;
|
||||||
|
Datum aclDatum = SysCacheGetAttr(DATABASEOID, databaseTuple, Anum_pg_database_datacl,
|
||||||
|
&isNull);
|
||||||
|
if (isNull)
|
||||||
|
{
|
||||||
|
ReleaseSysCache(databaseTuple);
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
Acl *acl = DatumGetAclPCopy(aclDatum);
|
||||||
|
AclItem *aclDat = ACL_DAT(acl);
|
||||||
|
int aclNum = ACL_NUM(acl);
|
||||||
|
List *commands = NIL;
|
||||||
|
|
||||||
|
ReleaseSysCache(databaseTuple);
|
||||||
|
|
||||||
|
for (int i = 0; i < aclNum; i++)
|
||||||
|
{
|
||||||
|
commands = list_concat(commands,
|
||||||
|
GenerateGrantOnDatabaseFromAclItem(
|
||||||
|
databaseOid, &aclDat[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GenerateGrantOnDatabaseFromAclItem generates a query string for replicating a users permissions
|
||||||
|
* on a database.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
GenerateGrantOnDatabaseFromAclItem(Oid databaseOid, AclItem *aclItem)
|
||||||
|
{
|
||||||
|
AclMode permissions = ACLITEM_GET_PRIVS(*aclItem) & ACL_ALL_RIGHTS_DATABASE;
|
||||||
|
AclMode grants = ACLITEM_GET_GOPTIONS(*aclItem) & ACL_ALL_RIGHTS_DATABASE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* seems unlikely but we check if there is a grant option in the list without the actual permission
|
||||||
|
*/
|
||||||
|
Assert(!(grants & ACL_CONNECT) || (permissions & ACL_CONNECT));
|
||||||
|
Assert(!(grants & ACL_CREATE) || (permissions & ACL_CREATE));
|
||||||
|
Assert(!(grants & ACL_CREATE_TEMP) || (permissions & ACL_CREATE_TEMP));
|
||||||
|
Oid granteeOid = aclItem->ai_grantee;
|
||||||
|
List *queries = NIL;
|
||||||
|
|
||||||
|
queries = lappend(queries, GenerateSetRoleQuery(aclItem->ai_grantor));
|
||||||
|
|
||||||
|
if (permissions & ACL_CONNECT)
|
||||||
|
{
|
||||||
|
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||||
|
OBJECT_DATABASE, granteeOid, databaseOid,
|
||||||
|
"CONNECT",
|
||||||
|
grants & ACL_CONNECT));
|
||||||
|
queries = lappend(queries, query);
|
||||||
|
}
|
||||||
|
if (permissions & ACL_CREATE)
|
||||||
|
{
|
||||||
|
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||||
|
OBJECT_DATABASE, granteeOid, databaseOid,
|
||||||
|
"CREATE",
|
||||||
|
grants & ACL_CREATE));
|
||||||
|
queries = lappend(queries, query);
|
||||||
|
}
|
||||||
|
if (permissions & ACL_CREATE_TEMP)
|
||||||
|
{
|
||||||
|
char *query = DeparseTreeNode((Node *) GenerateGrantStmtForRights(
|
||||||
|
OBJECT_DATABASE, granteeOid, databaseOid,
|
||||||
|
"TEMPORARY",
|
||||||
|
grants & ACL_CREATE_TEMP));
|
||||||
|
queries = lappend(queries, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = lappend(queries, "RESET ROLE");
|
||||||
|
|
||||||
|
return queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GenerateGrantStmtForRights is the function for creating GrantStmt's for all
|
* GenerateGrantStmtForRights is the function for creating GrantStmt's for all
|
||||||
* types of objects that are supported. It takes parameters to fill a GrantStmt's
|
* types of objects that are supported. It takes parameters to fill a GrantStmt's
|
||||||
|
@ -2116,6 +2207,11 @@ GetObjectsForGrantStmt(ObjectType objectType, Oid objectId)
|
||||||
return list_make1(sequence);
|
return list_make1(sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case OBJECT_DATABASE:
|
||||||
|
{
|
||||||
|
return list_make1(makeString(get_database_name(objectId)));
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
elog(ERROR, "unsupported object type for GRANT");
|
elog(ERROR, "unsupported object type for GRANT");
|
||||||
|
@ -3889,6 +3985,70 @@ citus_internal_update_none_dist_table_metadata(PG_FUNCTION_ARGS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* citus_internal_database_command is an internal UDF to
|
||||||
|
* create a database in an idempotent maner without
|
||||||
|
* transaction block restrictions.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
citus_internal_database_command(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
|
||||||
|
if (!ShouldSkipMetadataChecks())
|
||||||
|
{
|
||||||
|
EnsureCitusInitiatedOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
PG_ENSURE_ARGNOTNULL(0, "command");
|
||||||
|
|
||||||
|
text *commandText = PG_GETARG_TEXT_P(0);
|
||||||
|
char *command = text_to_cstring(commandText);
|
||||||
|
Node *parseTree = ParseTreeNode(command);
|
||||||
|
|
||||||
|
int saveNestLevel = NewGUCNestLevel();
|
||||||
|
|
||||||
|
set_config_option("citus.enable_ddl_propagation", "off",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
set_config_option("citus.enable_create_database_propagation", "off",
|
||||||
|
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
|
||||||
|
GUC_ACTION_LOCAL, true, 0, false);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* createdb() uses ParseState to report the error position for the
|
||||||
|
* input command and the position is reported to be 0 when it's provided as NULL.
|
||||||
|
* We're okay with that because we don't expect this UDF to be called with an incorrect
|
||||||
|
* DDL command.
|
||||||
|
*/
|
||||||
|
ParseState *pstate = NULL;
|
||||||
|
|
||||||
|
if (IsA(parseTree, CreatedbStmt))
|
||||||
|
{
|
||||||
|
CreatedbStmt *stmt = castNode(CreatedbStmt, parseTree);
|
||||||
|
|
||||||
|
bool missingOk = true;
|
||||||
|
Oid databaseOid = get_database_oid(stmt->dbname, missingOk);
|
||||||
|
|
||||||
|
if (!OidIsValid(databaseOid))
|
||||||
|
{
|
||||||
|
createdb(pstate, (CreatedbStmt *) parseTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("citus_internal_database_command() can only be used "
|
||||||
|
"for CREATE DATABASE command by Citus.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rollback GUCs to the state before this session */
|
||||||
|
AtEOXact_GUC(true, saveNestLevel);
|
||||||
|
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SyncNewColocationGroup synchronizes a new pg_dist_colocation entry to a worker.
|
* SyncNewColocationGroup synchronizes a new pg_dist_colocation entry to a worker.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1274,6 +1274,17 @@ RegisterCitusConfigVariables(void)
|
||||||
GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
|
GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE,
|
||||||
NULL, NULL, NULL);
|
NULL, NULL, NULL);
|
||||||
|
|
||||||
|
DefineCustomBoolVariable(
|
||||||
|
"citus.enable_create_database_propagation",
|
||||||
|
gettext_noop("Enables propagating CREATE DATABASE "
|
||||||
|
"and DROP DATABASE statements to workers."),
|
||||||
|
NULL,
|
||||||
|
&EnableCreateDatabasePropagation,
|
||||||
|
false,
|
||||||
|
PGC_USERSET,
|
||||||
|
GUC_STANDARD,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
|
||||||
DefineCustomBoolVariable(
|
DefineCustomBoolVariable(
|
||||||
"citus.enable_create_role_propagation",
|
"citus.enable_create_role_propagation",
|
||||||
gettext_noop("Enables propagating CREATE ROLE "
|
gettext_noop("Enables propagating CREATE ROLE "
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
-- citus--12.1-1--12.2-1
|
-- citus--12.1-1--12.2-1
|
||||||
|
|
||||||
-- bump version to 12.2-1
|
-- bump version to 12.2-1
|
||||||
|
|
||||||
|
#include "udfs/citus_internal_database_command/12.2-1.sql"
|
||||||
#include "udfs/citus_add_rebalance_strategy/12.2-1.sql"
|
#include "udfs/citus_add_rebalance_strategy/12.2-1.sql"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
-- citus--12.2-1--12.1-1
|
-- citus--12.2-1--12.1-1
|
||||||
|
|
||||||
|
DROP FUNCTION pg_catalog.citus_internal_database_command(text);
|
||||||
|
|
||||||
#include "../udfs/citus_add_rebalance_strategy/10.1-1.sql"
|
#include "../udfs/citus_add_rebalance_strategy/10.1-1.sql"
|
||||||
|
|
10
src/backend/distributed/sql/udfs/citus_internal_database_command/12.2-1.sql
generated
Normal file
10
src/backend/distributed/sql/udfs/citus_internal_database_command/12.2-1.sql
generated
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
--
|
||||||
|
-- citus_internal_database_command run given database command without transaction block restriction.
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_database_command(command text)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE C
|
||||||
|
VOLATILE
|
||||||
|
AS 'MODULE_PATHNAME', $$citus_internal_database_command$$;
|
||||||
|
COMMENT ON FUNCTION pg_catalog.citus_internal_database_command(text) IS
|
||||||
|
'run a database command without transaction block restrictions';
|
|
@ -0,0 +1,10 @@
|
||||||
|
--
|
||||||
|
-- citus_internal_database_command run given database command without transaction block restriction.
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION pg_catalog.citus_internal_database_command(command text)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE C
|
||||||
|
VOLATILE
|
||||||
|
AS 'MODULE_PATHNAME', $$citus_internal_database_command$$;
|
||||||
|
COMMENT ON FUNCTION pg_catalog.citus_internal_database_command(text) IS
|
||||||
|
'run a database command without transaction block restrictions';
|
|
@ -234,6 +234,18 @@ extern List * PreprocessAlterDatabaseRefreshCollStmt(Node *node, const char *que
|
||||||
extern List * PreprocessAlterDatabaseSetStmt(Node *node, const char *queryString,
|
extern List * PreprocessAlterDatabaseSetStmt(Node *node, const char *queryString,
|
||||||
ProcessUtilityContext processUtilityContext);
|
ProcessUtilityContext processUtilityContext);
|
||||||
|
|
||||||
|
extern List * PreprocessCreateDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext);
|
||||||
|
extern List * PostprocessCreateDatabaseStmt(Node *node, const char *queryString);
|
||||||
|
extern List * PreprocessDropDatabaseStmt(Node *node, const char *queryString,
|
||||||
|
ProcessUtilityContext processUtilityContext);
|
||||||
|
extern List * DropDatabaseStmtObjectAddress(Node *node, bool missingOk,
|
||||||
|
bool isPostprocess);
|
||||||
|
extern List * CreateDatabaseStmtObjectAddress(Node *node, bool missingOk,
|
||||||
|
bool isPostprocess);
|
||||||
|
extern void EnsureSupportedCreateDatabaseCommand(CreatedbStmt *stmt);
|
||||||
|
extern char * CreateDatabaseDDLCommand(Oid dbId);
|
||||||
|
|
||||||
|
|
||||||
/* domain.c - forward declarations */
|
/* domain.c - forward declarations */
|
||||||
extern List * CreateDomainStmtObjectAddress(Node *node, bool missing_ok, bool
|
extern List * CreateDomainStmtObjectAddress(Node *node, bool missing_ok, bool
|
||||||
|
|
|
@ -40,6 +40,7 @@ typedef enum
|
||||||
extern PropSetCmdBehavior PropagateSetCommands;
|
extern PropSetCmdBehavior PropagateSetCommands;
|
||||||
extern bool EnableDDLPropagation;
|
extern bool EnableDDLPropagation;
|
||||||
extern int CreateObjectPropagationMode;
|
extern int CreateObjectPropagationMode;
|
||||||
|
extern bool EnableCreateDatabasePropagation;
|
||||||
extern bool EnableCreateTypePropagation;
|
extern bool EnableCreateTypePropagation;
|
||||||
extern bool EnableCreateRolePropagation;
|
extern bool EnableCreateRolePropagation;
|
||||||
extern bool EnableAlterRolePropagation;
|
extern bool EnableAlterRolePropagation;
|
||||||
|
@ -93,6 +94,7 @@ extern void ProcessUtilityParseTree(Node *node, const char *queryString,
|
||||||
extern void MarkInvalidateForeignKeyGraph(void);
|
extern void MarkInvalidateForeignKeyGraph(void);
|
||||||
extern void InvalidateForeignKeyGraphForDDL(void);
|
extern void InvalidateForeignKeyGraphForDDL(void);
|
||||||
extern List * DDLTaskList(Oid relationId, const char *commandString);
|
extern List * DDLTaskList(Oid relationId, const char *commandString);
|
||||||
|
extern List * NontransactionalNodeDDLTaskList(TargetWorkerSet targets, List *commands);
|
||||||
extern List * NodeDDLTaskList(TargetWorkerSet targets, List *commands);
|
extern List * NodeDDLTaskList(TargetWorkerSet targets, List *commands);
|
||||||
extern bool AlterTableInProgress(void);
|
extern bool AlterTableInProgress(void);
|
||||||
extern bool DropSchemaOrDBInProgress(void);
|
extern bool DropSchemaOrDBInProgress(void);
|
||||||
|
|
|
@ -121,6 +121,28 @@ extern void AppendGrantedByInGrant(StringInfo buf, GrantStmt *stmt);
|
||||||
extern void AppendGrantSharedPrefix(StringInfo buf, GrantStmt *stmt);
|
extern void AppendGrantSharedPrefix(StringInfo buf, GrantStmt *stmt);
|
||||||
extern void AppendGrantSharedSuffix(StringInfo buf, GrantStmt *stmt);
|
extern void AppendGrantSharedSuffix(StringInfo buf, GrantStmt *stmt);
|
||||||
|
|
||||||
|
/* Common deparser utils */
|
||||||
|
|
||||||
|
typedef struct DefElemOptionFormat
|
||||||
|
{
|
||||||
|
char *name;
|
||||||
|
char *format;
|
||||||
|
int type;
|
||||||
|
} DefElemOptionFormat;
|
||||||
|
|
||||||
|
typedef enum OptionFormatType
|
||||||
|
{
|
||||||
|
OPTION_FORMAT_STRING,
|
||||||
|
OPTION_FORMAT_LITERAL_CSTR,
|
||||||
|
OPTION_FORMAT_BOOLEAN,
|
||||||
|
OPTION_FORMAT_INTEGER
|
||||||
|
} OptionFormatType;
|
||||||
|
|
||||||
|
|
||||||
|
extern void DefElemOptionToStatement(StringInfo buf, DefElem *option,
|
||||||
|
const DefElemOptionFormat *opt_formats,
|
||||||
|
int opt_formats_len);
|
||||||
|
|
||||||
|
|
||||||
/* forward declarations for deparse_statistics_stmts.c */
|
/* forward declarations for deparse_statistics_stmts.c */
|
||||||
extern char * DeparseCreateStatisticsStmt(Node *node);
|
extern char * DeparseCreateStatisticsStmt(Node *node);
|
||||||
|
@ -227,6 +249,8 @@ extern char * DeparseGrantOnDatabaseStmt(Node *node);
|
||||||
extern char * DeparseAlterDatabaseStmt(Node *node);
|
extern char * DeparseAlterDatabaseStmt(Node *node);
|
||||||
extern char * DeparseAlterDatabaseRefreshCollStmt(Node *node);
|
extern char * DeparseAlterDatabaseRefreshCollStmt(Node *node);
|
||||||
extern char * DeparseAlterDatabaseSetStmt(Node *node);
|
extern char * DeparseAlterDatabaseSetStmt(Node *node);
|
||||||
|
extern char * DeparseCreateDatabaseStmt(Node *node);
|
||||||
|
extern char * DeparseDropDatabaseStmt(Node *node);
|
||||||
|
|
||||||
|
|
||||||
/* forward declaration for deparse_publication_stmts.c */
|
/* forward declaration for deparse_publication_stmts.c */
|
||||||
|
|
|
@ -26,6 +26,7 @@ extern void MarkObjectDistributed(const ObjectAddress *distAddress);
|
||||||
extern void MarkObjectDistributedViaSuperUser(const ObjectAddress *distAddress);
|
extern void MarkObjectDistributedViaSuperUser(const ObjectAddress *distAddress);
|
||||||
extern void MarkObjectDistributedLocally(const ObjectAddress *distAddress);
|
extern void MarkObjectDistributedLocally(const ObjectAddress *distAddress);
|
||||||
extern void UnmarkObjectDistributed(const ObjectAddress *address);
|
extern void UnmarkObjectDistributed(const ObjectAddress *address);
|
||||||
|
extern void UnmarkNodeWideObjectsDistributed(Node *node);
|
||||||
extern bool IsTableOwnedByExtension(Oid relationId);
|
extern bool IsTableOwnedByExtension(Oid relationId);
|
||||||
extern bool ObjectAddressDependsOnExtension(const ObjectAddress *target);
|
extern bool ObjectAddressDependsOnExtension(const ObjectAddress *target);
|
||||||
extern bool IsAnyObjectAddressOwnedByExtension(const List *targets,
|
extern bool IsAnyObjectAddressOwnedByExtension(const List *targets,
|
||||||
|
|
|
@ -107,6 +107,7 @@ extern char * ColocationIdUpdateCommand(Oid relationId, uint32 colocationId);
|
||||||
extern char * CreateSchemaDDLCommand(Oid schemaId);
|
extern char * CreateSchemaDDLCommand(Oid schemaId);
|
||||||
extern List * GrantOnSchemaDDLCommands(Oid schemaId);
|
extern List * GrantOnSchemaDDLCommands(Oid schemaId);
|
||||||
extern List * GrantOnFunctionDDLCommands(Oid functionOid);
|
extern List * GrantOnFunctionDDLCommands(Oid functionOid);
|
||||||
|
extern List * GrantOnDatabaseDDLCommands(Oid databaseOid);
|
||||||
extern List * GrantOnForeignServerDDLCommands(Oid serverId);
|
extern List * GrantOnForeignServerDDLCommands(Oid serverId);
|
||||||
extern List * GenerateGrantOnForeignServerQueriesFromAclItem(Oid serverId,
|
extern List * GenerateGrantOnForeignServerQueriesFromAclItem(Oid serverId,
|
||||||
AclItem *aclItem);
|
AclItem *aclItem);
|
||||||
|
|
|
@ -0,0 +1,962 @@
|
||||||
|
-- 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);
|
||||||
|
ERROR: This is an internal Citus function can only be used in a distributed transaction
|
||||||
|
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();
|
||||||
|
pg_reload_conf
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT pg_sleep(0.1);
|
||||||
|
pg_sleep
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SET ROLE test_db_commands;
|
||||||
|
-- fails on null input
|
||||||
|
SELECT pg_catalog.citus_internal_database_command(null);
|
||||||
|
ERROR: command cannot be NULL
|
||||||
|
-- fails on non create / drop db command
|
||||||
|
SELECT pg_catalog.citus_internal_database_command('CREATE TABLE foo_bar(a int)');
|
||||||
|
ERROR: citus_internal_database_command() can only be used for CREATE DATABASE command by Citus.
|
||||||
|
SELECT pg_catalog.citus_internal_database_command('SELECT 1');
|
||||||
|
ERROR: citus_internal_database_command() can only be used for CREATE DATABASE command by Citus.
|
||||||
|
SELECT pg_catalog.citus_internal_database_command('asfsfdsg');
|
||||||
|
ERROR: syntax error at or near "asfsfdsg"
|
||||||
|
SELECT pg_catalog.citus_internal_database_command('');
|
||||||
|
ERROR: cannot execute multiple utility events
|
||||||
|
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');
|
||||||
|
ERROR: permission denied to create database
|
||||||
|
RESET ROLE;
|
||||||
|
DROP USER test_db_commands;
|
||||||
|
ALTER SYSTEM RESET citus.enable_manual_metadata_changes_for_user;
|
||||||
|
SELECT pg_reload_conf();
|
||||||
|
pg_reload_conf
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT pg_sleep(0.1);
|
||||||
|
pg_sleep
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
\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;
|
||||||
|
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
|
||||||
|
DETAIL: Citus does not propagate CREATE DATABASE command to workers
|
||||||
|
HINT: You can manually create a database and its extensions on workers.
|
||||||
|
-- check that it's only created for coordinator
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "local_database", "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": 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)
|
||||||
|
|
||||||
|
DROP DATABASE local_database;
|
||||||
|
-- and is dropped
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;
|
||||||
|
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)
|
||||||
|
|
||||||
|
\c - - - :worker_1_port
|
||||||
|
CREATE DATABASE local_database;
|
||||||
|
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
|
||||||
|
DETAIL: Citus does not propagate CREATE DATABASE command to workers
|
||||||
|
HINT: You can manually create a database and its extensions on workers.
|
||||||
|
-- check that it's only created for coordinator
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
worker node (local) | {"database_properties": {"datacl": null, "datname": "local_database", "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": 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)
|
||||||
|
|
||||||
|
DROP DATABASE local_database;
|
||||||
|
-- and is dropped
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('local_database') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (remote) | {"database_properties": null, "pg_dist_object_record_for_db_exists": false, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
worker node (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}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
\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;
|
||||||
|
ERROR: Only template1 is supported as template parameter for CREATE DATABASE
|
||||||
|
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;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "create_drop_db_tablespace", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "create_drop_db_tablespace", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "create_drop_db_tablespace", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- 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';
|
||||||
|
ERROR: new collation (C.UTF-8) is incompatible with the collation of the template database (C)
|
||||||
|
HINT: Use the same collation as in the template database, or use template0 as template.
|
||||||
|
CREATE DATABASE lc_ctype_test LC_CTYPE = 'C.UTF-8';
|
||||||
|
ERROR: new LC_CTYPE (C.UTF-8) is incompatible with the LC_CTYPE of the template database (C)
|
||||||
|
HINT: Use the same LC_CTYPE as in the template database, or use template0 as template.
|
||||||
|
CREATE DATABASE locale_test LOCALE = 'C.UTF-8';
|
||||||
|
ERROR: new collation (C.UTF-8) is incompatible with the collation of the template database (C)
|
||||||
|
HINT: Use the same collation as in the template database, or use template0 as template.
|
||||||
|
CREATE DATABASE lc_collate_lc_ctype_test LC_COLLATE = 'C.UTF-8' LC_CTYPE = 'C.UTF-8';
|
||||||
|
ERROR: new collation (C.UTF-8) is incompatible with the collation of the template database (C)
|
||||||
|
HINT: Use the same collation as in the template database, or use template0 as template.
|
||||||
|
-- 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;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "lc_collate_test", "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": "lc_collate_test", "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": "lc_collate_test", "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)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('lc_ctype_test') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "lc_ctype_test", "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": "lc_ctype_test", "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": "lc_ctype_test", "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)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('locale_test') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "locale_test", "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": "locale_test", "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": "locale_test", "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)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('lc_collate_lc_ctype_test') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "lc_collate_lc_ctype_test", "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": "lc_collate_lc_ctype_test", "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": "lc_collate_lc_ctype_test", "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 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"
|
||||||
|
$$
|
||||||
|
);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
ALTER TABLESPACE
|
||||||
|
ALTER TABLESPACE
|
||||||
|
ALTER TABLESPACE
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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
|
||||||
|
$$
|
||||||
|
);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
ALTER DATABASE
|
||||||
|
ALTER DATABASE
|
||||||
|
ALTER DATABASE
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- test database syncing after node addition
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
--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;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SET citus.metadata_sync_mode to 'transactional';
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SET citus.metadata_sync_mode to 'nontransactional';
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
RESET citus.metadata_sync_mode;
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT citus_disable_node_and_wait('localhost', :worker_1_port, true);
|
||||||
|
citus_disable_node_and_wait
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
CREATE DATABASE test_node_activation;
|
||||||
|
SELECT 1 FROM citus_activate_node('localhost', :worker_1_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('mydatabase_1') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "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": "mydatabase_1", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": 10, "daticulocale": null, "datistemplate": false, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('db_needs_escape') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "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": "db_needs_escape", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "role-needs\\!escape", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_node_activation') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "test_node_activation", "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": "test_node_activation", "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": "test_node_activation", "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)
|
||||||
|
|
||||||
|
SET citus.log_remote_commands = true;
|
||||||
|
set citus.grep_remote_commands = '%DROP DATABASE%';
|
||||||
|
drop database mydatabase;
|
||||||
|
NOTICE: issuing DROP DATABASE mydatabase
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
NOTICE: issuing DROP DATABASE mydatabase
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
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;
|
||||||
|
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 * FROM public.check_database_on_all_nodes('mydatabase') ORDER BY node_type;
|
||||||
|
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 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;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "my_template_database", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": -1, "daticulocale": null, "datistemplate": true, "database_owner": "create_drop_db_test_user", "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": "my_template_database", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": -1, "daticulocale": null, "datistemplate": true, "database_owner": "create_drop_db_test_user", "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": "my_template_database", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "ts-needs\\!escape", "daticurules": null, "datallowconn": false, "datconnlimit": -1, "daticulocale": null, "datistemplate": true, "database_owner": "create_drop_db_test_user", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
--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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
UPDATE 1
|
||||||
|
UPDATE 1
|
||||||
|
UPDATE 1
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SET citus.log_remote_commands = true;
|
||||||
|
set citus.grep_remote_commands = '%DROP DATABASE%';
|
||||||
|
drop database my_template_database;
|
||||||
|
NOTICE: issuing DROP DATABASE my_template_database
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
NOTICE: issuing DROP DATABASE my_template_database
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
SET citus.log_remote_commands = false;
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('my_template_database') ORDER BY node_type;
|
||||||
|
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)
|
||||||
|
|
||||||
|
--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";
|
||||||
|
NOTICE: issuing CREATE DATABASE "mydatabase#1'2"
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
NOTICE: issuing CREATE DATABASE "mydatabase#1'2"
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
set citus.grep_remote_commands = '%DROP DATABASE%';
|
||||||
|
drop database if exists "mydatabase#1'2";
|
||||||
|
NOTICE: issuing DROP DATABASE IF EXISTS "mydatabase#1'2"
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
NOTICE: issuing DROP DATABASE IF EXISTS "mydatabase#1'2"
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
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";
|
||||||
|
NOTICE: database "mydatabase#1'2" does not exist, skipping
|
||||||
|
-- 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";
|
||||||
|
ERROR: database "mydatabase#1'2" does not exist
|
||||||
|
\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;
|
||||||
|
ERROR: operation is not allowed on this node
|
||||||
|
HINT: Connect to the coordinator and run it again.
|
||||||
|
-- and the same applies to create database too
|
||||||
|
create database error_test;
|
||||||
|
ERROR: operation is not allowed on this node
|
||||||
|
HINT: Connect to the coordinator and run it again.
|
||||||
|
\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);
|
||||||
|
NOTICE: issuing DROP DATABASE db_force_test WITH ( FORCE )
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
NOTICE: issuing DROP DATABASE db_force_test WITH ( FORCE )
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
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;
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- test that we won't propagate non-distributed databases in citus_add_node
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SET citus.enable_create_database_propagation TO off;
|
||||||
|
CREATE DATABASE non_distributed_db;
|
||||||
|
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
|
||||||
|
DETAIL: Citus does not propagate CREATE DATABASE command to workers
|
||||||
|
HINT: You can manually create a database and its extensions on workers.
|
||||||
|
SET citus.enable_create_database_propagation TO on;
|
||||||
|
create database distributed_db;
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
--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;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "non_distributed_db", "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": 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)
|
||||||
|
|
||||||
|
--distributed_db should be propagated to worker_2
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('distributed_db') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "distributed_db", "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": "distributed_db", "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": "distributed_db", "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)
|
||||||
|
|
||||||
|
--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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
CREATE DATABASE
|
||||||
|
CREATE DATABASE
|
||||||
|
CREATE DATABASE
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
ERROR: operation is not allowed on this node
|
||||||
|
ERROR: operation is not allowed on this node
|
||||||
|
REVOKE
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
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;
|
||||||
|
NOTICE: issuing SELECT worker_create_or_alter_role('db_role_grants_test_role_missing_on_node_2', 'CREATE ROLE db_role_grants_test_role_missing_on_node_2', 'ALTER ROLE db_role_grants_test_role_missing_on_node_2')
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
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;
|
||||||
|
NOTICE: issuing GRANT connect, temporary, create ON DATABASE db_role_grants_test TO db_role_grants_test_role_exists_on_node_2;
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test to db_role_grants_test_role_missing_on_node_2;
|
||||||
|
NOTICE: issuing GRANT connect, temporary, create ON DATABASE db_role_grants_test TO db_role_grants_test_role_missing_on_node_2;
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test_non_distributed to db_role_grants_test_role_exists_on_node_2;
|
||||||
|
NOTICE: issuing GRANT connect, temporary, create ON DATABASE db_role_grants_test_non_distributed TO db_role_grants_test_role_exists_on_node_2;
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
grant CONNECT,TEMPORARY,CREATE on DATABASE db_role_grants_test_non_distributed to db_role_grants_test_role_missing_on_node_2;
|
||||||
|
NOTICE: issuing GRANT connect, temporary, create ON DATABASE db_role_grants_test_non_distributed TO db_role_grants_test_role_missing_on_node_2;
|
||||||
|
DETAIL: on server postgres@localhost:xxxxx connectionId: xxxxxxx
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
RESET citus.log_remote_commands;
|
||||||
|
RESET citus.grep_remote_commands;
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
t
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
f
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
f
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
f
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
f
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
f
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
f
|
||||||
|
t
|
||||||
|
t
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
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;
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
DROP DATABASE
|
||||||
|
DROP DATABASE
|
||||||
|
DROP DATABASE
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
DROP ROLE db_role_grants_test_role_exists_on_node_2;
|
||||||
|
DROP ROLE db_role_grants_test_role_missing_on_node_2;
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
set citus.enable_create_role_propagation TO off;
|
||||||
|
create role non_propagated_role;
|
||||||
|
NOTICE: not propagating CREATE ROLE/USER commands to other nodes
|
||||||
|
HINT: Connect to other nodes directly to manually create all necessary users and roles.
|
||||||
|
set citus.enable_create_role_propagation TO on;
|
||||||
|
set citus.enable_create_database_propagation TO on;
|
||||||
|
-- Make sure that we propagate non_propagated_role because it's a dependency of test_db.
|
||||||
|
-- And hence it becomes a distributed object.
|
||||||
|
create database test_db OWNER non_propagated_role;
|
||||||
|
create role propagated_role;
|
||||||
|
grant connect on database test_db to propagated_role;
|
||||||
|
SELECT 1 FROM citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_db') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": ["=Tc/non_propagated_role", "non_propagated_role=CTc/non_propagated_role", "propagated_role=c/non_propagated_role"], "datname": "test_db", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "non_propagated_role", "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": ["=Tc/non_propagated_role", "non_propagated_role=CTc/non_propagated_role", "propagated_role=c/non_propagated_role"], "datname": "test_db", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "non_propagated_role", "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": ["=Tc/non_propagated_role", "non_propagated_role=CTc/non_propagated_role", "propagated_role=c/non_propagated_role"], "datname": "test_db", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": true, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "non_propagated_role", "datcollversion": null, "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
REVOKE CONNECT ON DATABASE test_db FROM propagated_role;
|
||||||
|
DROP DATABASE test_db;
|
||||||
|
DROP ROLE propagated_role, non_propagated_role;
|
||||||
|
--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"
|
||||||
|
$$
|
||||||
|
);
|
||||||
|
result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
DROP TABLESPACE
|
||||||
|
DROP TABLESPACE
|
||||||
|
DROP TABLESPACE
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
drop user create_drop_db_test_user;
|
||||||
|
reset citus.enable_create_database_propagation;
|
|
@ -0,0 +1,82 @@
|
||||||
|
--
|
||||||
|
-- PG15
|
||||||
|
--
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_15
|
||||||
|
\else
|
||||||
|
\q
|
||||||
|
\endif
|
||||||
|
-- create/drop database for pg >= 15
|
||||||
|
set citus.enable_create_database_propagation=on;
|
||||||
|
CREATE DATABASE mydatabase
|
||||||
|
WITH OID = 966345;
|
||||||
|
ERROR: CREATE DATABASE option "oid" is not supported
|
||||||
|
CREATE DATABASE mydatabase
|
||||||
|
WITH strategy file_copy;
|
||||||
|
ERROR: Only wal_log is supported as strategy parameter for CREATE DATABASE
|
||||||
|
CREATE DATABASE st_wal_log
|
||||||
|
WITH strategy WaL_LoG;
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('st_wal_log') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "st_wal_log", "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": "st_wal_log", "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": "st_wal_log", "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 st_wal_log;
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- test COLLATION_VERSION
|
||||||
|
CREATE DATABASE test_collation_version
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
COLLATION_VERSION = '1.0'
|
||||||
|
ALLOW_CONNECTIONS = false;
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
?column?
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_collation_version') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "test_collation_version", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": false, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "postgres", "datcollversion": "1.0", "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": "test_collation_version", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": false, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "postgres", "datcollversion": "1.0", "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": "test_collation_version", "datctype": "C", "encoding": "UTF8", "datcollate": "C", "tablespace": "pg_default", "daticurules": null, "datallowconn": false, "datconnlimit": -1, "daticulocale": null, "datistemplate": false, "database_owner": "postgres", "datcollversion": "1.0", "datlocprovider": "c"}, "pg_dist_object_record_for_db_exists": true, "stale_pg_dist_object_record_for_a_db_exists": false}
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
drop database test_collation_version;
|
||||||
|
SET client_min_messages TO WARNING;
|
||||||
|
-- test LOCALE_PROVIDER & ICU_LOCALE
|
||||||
|
CREATE DATABASE test_locale_provider
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
LOCALE_PROVIDER = 'icu'
|
||||||
|
ICU_LOCALE = 'en_US';
|
||||||
|
ERROR: new locale provider (icu) does not match locale provider of the template database (libc)
|
||||||
|
HINT: Use the same locale provider as in the template database, or use template0 as template.
|
||||||
|
RESET client_min_messages;
|
||||||
|
CREATE DATABASE test_locale_provider
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
LOCALE_PROVIDER = 'libc'
|
||||||
|
ICU_LOCALE = 'en_US';
|
||||||
|
ERROR: ICU locale cannot be specified unless locale provider is ICU
|
||||||
|
CREATE DATABASE test_locale_provider
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
LOCALE_PROVIDER = 'libc';
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_locale_provider') ORDER BY node_type;
|
||||||
|
node_type | result
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
coordinator (local) | {"database_properties": {"datacl": null, "datname": "test_locale_provider", "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": "test_locale_provider", "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": "test_locale_provider", "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 test_locale_provider;
|
||||||
|
\c - - - :master_port
|
|
@ -0,0 +1,9 @@
|
||||||
|
--
|
||||||
|
-- PG15
|
||||||
|
--
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_15
|
||||||
|
\else
|
||||||
|
\q
|
|
@ -0,0 +1,23 @@
|
||||||
|
--
|
||||||
|
-- PG16
|
||||||
|
--
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 16 AS server_version_ge_16
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_16
|
||||||
|
\else
|
||||||
|
\q
|
||||||
|
\endif
|
||||||
|
-- create/drop database for pg >= 16
|
||||||
|
set citus.enable_create_database_propagation=on;
|
||||||
|
-- test icu_rules
|
||||||
|
--
|
||||||
|
-- practically we don't support it but better to test
|
||||||
|
CREATE DATABASE citus_icu_rules_test WITH icu_rules='de_DE@collation=phonebook';
|
||||||
|
ERROR: ICU rules cannot be specified unless locale provider is ICU
|
||||||
|
CREATE DATABASE citus_icu_rules_test WITH icu_rules='de_DE@collation=phonebook' locale_provider='icu';
|
||||||
|
ERROR: LOCALE or ICU_LOCALE must be specified
|
||||||
|
CREATE DATABASE citus_icu_rules_test WITH icu_rules='de_DE@collation=phonebook' locale_provider='icu' icu_locale = 'de_DE';
|
||||||
|
NOTICE: using standard form "de-DE" for ICU locale "de_DE"
|
||||||
|
ERROR: new locale provider (icu) does not match locale provider of the template database (libc)
|
||||||
|
HINT: Use the same locale provider as in the template database, or use template0 as template.
|
|
@ -0,0 +1,9 @@
|
||||||
|
--
|
||||||
|
-- PG16
|
||||||
|
--
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 16 AS server_version_ge_16
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_16
|
||||||
|
\else
|
||||||
|
\q
|
|
@ -26,8 +26,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="CREATE").kill()');
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open
|
ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open
|
||||||
SELECT citus.mitmproxy('conn.allow()');
|
SELECT citus.mitmproxy('conn.allow()');
|
||||||
mitmproxy
|
mitmproxy
|
||||||
|
@ -59,8 +60,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="CREATE").kill()');
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open
|
ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open
|
||||||
SELECT citus.mitmproxy('conn.allow()');
|
SELECT citus.mitmproxy('conn.allow()');
|
||||||
mitmproxy
|
mitmproxy
|
||||||
|
@ -86,8 +88,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="CREATE").cancel(' || pg_backend_pid(
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: canceling statement due to user request
|
ERROR: canceling statement due to user request
|
||||||
SELECT citus.mitmproxy('conn.allow()');
|
SELECT citus.mitmproxy('conn.allow()');
|
||||||
mitmproxy
|
mitmproxy
|
||||||
|
@ -111,8 +114,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="CREATE").cancel(' || pg_backend_pid(
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
CREATE INDEX CONCURRENTLY idx_index_test ON index_test(id, value_1);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: canceling statement due to user request
|
ERROR: canceling statement due to user request
|
||||||
SELECT citus.mitmproxy('conn.allow()');
|
SELECT citus.mitmproxy('conn.allow()');
|
||||||
mitmproxy
|
mitmproxy
|
||||||
|
@ -137,8 +141,9 @@ SELECT citus.mitmproxy('conn.onQuery(query="DROP INDEX CONCURRENTLY").kill()');
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
DROP INDEX CONCURRENTLY IF EXISTS idx_index_test;
|
DROP INDEX CONCURRENTLY IF EXISTS idx_index_test;
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open
|
ERROR: connection to the remote node localhost:xxxxx failed with the following error: connection not open
|
||||||
SELECT citus.mitmproxy('conn.allow()');
|
SELECT citus.mitmproxy('conn.allow()');
|
||||||
mitmproxy
|
mitmproxy
|
||||||
|
@ -164,8 +169,9 @@ SELECT create_distributed_table('index_test_2', 'a');
|
||||||
|
|
||||||
INSERT INTO index_test_2 VALUES (1, 1), (1, 2);
|
INSERT INTO index_test_2 VALUES (1, 1), (1, 2);
|
||||||
CREATE UNIQUE INDEX CONCURRENTLY index_test_2_a_idx ON index_test_2(a);
|
CREATE UNIQUE INDEX CONCURRENTLY index_test_2_a_idx ON index_test_2(a);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: could not create unique index "index_test_2_a_idx_1880019"
|
ERROR: could not create unique index "index_test_2_a_idx_1880019"
|
||||||
DETAIL: Key (a)=(1) is duplicated.
|
DETAIL: Key (a)=(1) is duplicated.
|
||||||
CONTEXT: while executing command on localhost:xxxxx
|
CONTEXT: while executing command on localhost:xxxxx
|
||||||
|
|
|
@ -1422,7 +1422,8 @@ ALTER EXTENSION citus UPDATE TO '12.2-1';
|
||||||
SELECT * FROM multi_extension.print_extension_changes();
|
SELECT * FROM multi_extension.print_extension_changes();
|
||||||
previous_object | current_object
|
previous_object | current_object
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
(0 rows)
|
| function citus_internal_database_command(text) void
|
||||||
|
(1 row)
|
||||||
|
|
||||||
DROP TABLE multi_extension.prev_objects, multi_extension.extension_diff;
|
DROP TABLE multi_extension.prev_objects, multi_extension.extension_diff;
|
||||||
-- show running version
|
-- show running version
|
||||||
|
|
|
@ -556,3 +556,73 @@ BEGIN
|
||||||
ORDER BY node_type;
|
ORDER BY node_type;
|
||||||
END;
|
END;
|
||||||
$func$ LANGUAGE plpgsql;
|
$func$ LANGUAGE plpgsql;
|
||||||
|
-- For all nodes, returns database properties of given database, except
|
||||||
|
-- oid, datfrozenxid and datminmxid.
|
||||||
|
--
|
||||||
|
-- Also returns whether the node has a pg_dist_object record for the database
|
||||||
|
-- and whether there are any stale pg_dist_object records for a database.
|
||||||
|
CREATE OR REPLACE FUNCTION check_database_on_all_nodes(p_database_name text)
|
||||||
|
RETURNS TABLE (node_type text, result text)
|
||||||
|
AS $func$
|
||||||
|
DECLARE
|
||||||
|
pg_ge_15_options text := '';
|
||||||
|
pg_ge_16_options text := '';
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_database'::regclass AND attname = 'datlocprovider') THEN
|
||||||
|
pg_ge_15_options := ', daticulocale, datcollversion, datlocprovider';
|
||||||
|
ELSE
|
||||||
|
pg_ge_15_options := $$, null as daticulocale, null as datcollversion, 'c' as datlocprovider$$;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_database'::regclass AND attname = 'daticurules') THEN
|
||||||
|
pg_ge_16_options := ', daticurules';
|
||||||
|
ELSE
|
||||||
|
pg_ge_16_options := ', null as daticurules';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
CASE WHEN (groupid = 0 AND groupid = (SELECT groupid FROM pg_dist_local_group)) THEN 'coordinator (local)'
|
||||||
|
WHEN (groupid = 0) THEN 'coordinator (remote)'
|
||||||
|
WHEN (groupid = (SELECT groupid FROM pg_dist_local_group)) THEN 'worker node (local)'
|
||||||
|
ELSE 'worker node (remote)'
|
||||||
|
END AS node_type,
|
||||||
|
q2.result
|
||||||
|
FROM run_command_on_all_nodes(
|
||||||
|
format(
|
||||||
|
$$
|
||||||
|
SELECT to_jsonb(q.*)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT to_jsonb(database_properties.*)
|
||||||
|
FROM (
|
||||||
|
SELECT datname, pa.rolname as database_owner,
|
||||||
|
pg_encoding_to_char(pd.encoding) as encoding,
|
||||||
|
datistemplate, datallowconn, datconnlimit, datacl,
|
||||||
|
pt.spcname AS tablespace, datcollate, datctype
|
||||||
|
%2$s -- >= pg15 options
|
||||||
|
%3$s -- >= pg16 options
|
||||||
|
FROM pg_database pd
|
||||||
|
JOIN pg_authid pa ON pd.datdba = pa.oid
|
||||||
|
JOIN pg_tablespace pt ON pd.dattablespace = pt.oid
|
||||||
|
WHERE datname = '%1$s'
|
||||||
|
) database_properties
|
||||||
|
) AS database_properties,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)=1
|
||||||
|
FROM pg_dist_object WHERE objid = (SELECT oid FROM pg_database WHERE datname = '%1$s')
|
||||||
|
) AS pg_dist_object_record_for_db_exists,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) > 0
|
||||||
|
FROM pg_dist_object
|
||||||
|
WHERE classid = 1262 AND objid NOT IN (SELECT oid FROM pg_database)
|
||||||
|
) AS stale_pg_dist_object_record_for_a_db_exists
|
||||||
|
) q
|
||||||
|
$$,
|
||||||
|
p_database_name, pg_ge_15_options, pg_ge_16_options
|
||||||
|
)
|
||||||
|
) q2
|
||||||
|
JOIN pg_dist_node USING (nodeid);
|
||||||
|
END;
|
||||||
|
$func$ LANGUAGE plpgsql;
|
|
@ -88,8 +88,9 @@ SELECT create_distributed_table('failover_to_local', 'a', shard_count=>32);
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE INDEX CONCURRENTLY ON failover_to_local(a);
|
CREATE INDEX CONCURRENTLY ON failover_to_local(a);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: the total number of connections on the server is more than max_connections(100)
|
ERROR: the total number of connections on the server is more than max_connections(100)
|
||||||
HINT: Consider using a higher value for max_connections
|
HINT: Consider using a higher value for max_connections
|
||||||
-- reset global GUC changes
|
-- reset global GUC changes
|
||||||
|
|
|
@ -88,8 +88,9 @@ SELECT create_distributed_table('failover_to_local', 'a', shard_count=>32);
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
CREATE INDEX CONCURRENTLY ON failover_to_local(a);
|
CREATE INDEX CONCURRENTLY ON failover_to_local(a);
|
||||||
WARNING: CONCURRENTLY-enabled index commands can fail partially, leaving behind an INVALID index.
|
WARNING: Commands that are not transaction-safe may result in partial failure, potentially leading to an inconsistent state.
|
||||||
Use DROP INDEX CONCURRENTLY IF EXISTS to remove the invalid index.
|
If the problematic command is a CREATE operation, consider using the 'IF EXISTS' syntax to drop the object,
|
||||||
|
if applicable, and then re-attempt the original command.
|
||||||
ERROR: the total number of connections on the server is more than max_connections(100)
|
ERROR: the total number of connections on the server is more than max_connections(100)
|
||||||
HINT: Consider using a higher value for max_connections
|
HINT: Consider using a higher value for max_connections
|
||||||
-- reset global GUC changes
|
-- reset global GUC changes
|
||||||
|
|
|
@ -71,6 +71,7 @@ ORDER BY 1;
|
||||||
function citus_internal_add_shard_metadata(regclass,bigint,"char",text,text)
|
function citus_internal_add_shard_metadata(regclass,bigint,"char",text,text)
|
||||||
function citus_internal_add_tenant_schema(oid,integer)
|
function citus_internal_add_tenant_schema(oid,integer)
|
||||||
function citus_internal_adjust_local_clock_to_remote(cluster_clock)
|
function citus_internal_adjust_local_clock_to_remote(cluster_clock)
|
||||||
|
function citus_internal_database_command(text)
|
||||||
function citus_internal_delete_colocation_metadata(integer)
|
function citus_internal_delete_colocation_metadata(integer)
|
||||||
function citus_internal_delete_partition_metadata(regclass)
|
function citus_internal_delete_partition_metadata(regclass)
|
||||||
function citus_internal_delete_placement_metadata(bigint)
|
function citus_internal_delete_placement_metadata(bigint)
|
||||||
|
@ -343,5 +344,5 @@ ORDER BY 1;
|
||||||
view citus_stat_tenants_local
|
view citus_stat_tenants_local
|
||||||
view pg_dist_shard_placement
|
view pg_dist_shard_placement
|
||||||
view time_partitions
|
view time_partitions
|
||||||
(333 rows)
|
(334 rows)
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,9 @@ test: alter_database_owner
|
||||||
test: seclabel
|
test: seclabel
|
||||||
test: distributed_triggers
|
test: distributed_triggers
|
||||||
test: create_single_shard_table
|
test: create_single_shard_table
|
||||||
|
test: create_drop_database_propagation
|
||||||
|
test: create_drop_database_propagation_pg15
|
||||||
|
test: create_drop_database_propagation_pg16
|
||||||
# don't parallelize single_shard_table_udfs to make sure colocation ids are sequential
|
# don't parallelize single_shard_table_udfs to make sure colocation ids are sequential
|
||||||
test: single_shard_table_udfs
|
test: single_shard_table_udfs
|
||||||
test: schema_based_sharding
|
test: schema_based_sharding
|
||||||
|
|
|
@ -634,7 +634,7 @@ for my $port (@followerWorkerPorts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for my $tablespace ("ts0", "ts1", "ts2")
|
for my $tablespace ("ts0", "ts1", "ts2", "ts3", "ts4", "ts5")
|
||||||
{
|
{
|
||||||
if (-e catfile($TMP_CHECKDIR, $tablespace))
|
if (-e catfile($TMP_CHECKDIR, $tablespace))
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,554 @@
|
||||||
|
|
||||||
|
-- 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;
|
||||||
|
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
|
||||||
|
set citus.enable_create_role_propagation TO off;
|
||||||
|
create role non_propagated_role;
|
||||||
|
set citus.enable_create_role_propagation TO on;
|
||||||
|
|
||||||
|
set citus.enable_create_database_propagation TO on;
|
||||||
|
|
||||||
|
-- Make sure that we propagate non_propagated_role because it's a dependency of test_db.
|
||||||
|
-- And hence it becomes a distributed object.
|
||||||
|
create database test_db OWNER non_propagated_role;
|
||||||
|
|
||||||
|
create role propagated_role;
|
||||||
|
grant connect on database test_db to propagated_role;
|
||||||
|
|
||||||
|
SELECT 1 FROM citus_add_node('localhost', :worker_2_port);
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_db') ORDER BY node_type;
|
||||||
|
|
||||||
|
REVOKE CONNECT ON DATABASE test_db FROM propagated_role;
|
||||||
|
DROP DATABASE test_db;
|
||||||
|
DROP ROLE propagated_role, non_propagated_role;
|
||||||
|
|
||||||
|
--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;
|
|
@ -0,0 +1,65 @@
|
||||||
|
--
|
||||||
|
-- PG15
|
||||||
|
--
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 15 AS server_version_ge_15
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_15
|
||||||
|
\else
|
||||||
|
\q
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- create/drop database for pg >= 15
|
||||||
|
|
||||||
|
set citus.enable_create_database_propagation=on;
|
||||||
|
|
||||||
|
CREATE DATABASE mydatabase
|
||||||
|
WITH OID = 966345;
|
||||||
|
|
||||||
|
CREATE DATABASE mydatabase
|
||||||
|
WITH strategy file_copy;
|
||||||
|
|
||||||
|
CREATE DATABASE st_wal_log
|
||||||
|
WITH strategy WaL_LoG;
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('st_wal_log') ORDER BY node_type;
|
||||||
|
|
||||||
|
drop database st_wal_log;
|
||||||
|
|
||||||
|
select 1 from citus_remove_node('localhost', :worker_2_port);
|
||||||
|
|
||||||
|
-- test COLLATION_VERSION
|
||||||
|
|
||||||
|
CREATE DATABASE test_collation_version
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
COLLATION_VERSION = '1.0'
|
||||||
|
ALLOW_CONNECTIONS = false;
|
||||||
|
|
||||||
|
select 1 from citus_add_node('localhost', :worker_2_port);
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_collation_version') ORDER BY node_type;
|
||||||
|
|
||||||
|
drop database test_collation_version;
|
||||||
|
|
||||||
|
SET client_min_messages TO WARNING;
|
||||||
|
-- test LOCALE_PROVIDER & ICU_LOCALE
|
||||||
|
CREATE DATABASE test_locale_provider
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
LOCALE_PROVIDER = 'icu'
|
||||||
|
ICU_LOCALE = 'en_US';
|
||||||
|
RESET client_min_messages;
|
||||||
|
|
||||||
|
CREATE DATABASE test_locale_provider
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
LOCALE_PROVIDER = 'libc'
|
||||||
|
ICU_LOCALE = 'en_US';
|
||||||
|
|
||||||
|
CREATE DATABASE test_locale_provider
|
||||||
|
WITH ENCODING = 'UTF8'
|
||||||
|
LOCALE_PROVIDER = 'libc';
|
||||||
|
|
||||||
|
SELECT * FROM public.check_database_on_all_nodes('test_locale_provider') ORDER BY node_type;
|
||||||
|
|
||||||
|
drop database test_locale_provider;
|
||||||
|
|
||||||
|
\c - - - :master_port
|
|
@ -0,0 +1,22 @@
|
||||||
|
--
|
||||||
|
-- PG16
|
||||||
|
--
|
||||||
|
SHOW server_version \gset
|
||||||
|
SELECT substring(:'server_version', '\d+')::int >= 16 AS server_version_ge_16
|
||||||
|
\gset
|
||||||
|
\if :server_version_ge_16
|
||||||
|
\else
|
||||||
|
\q
|
||||||
|
\endif
|
||||||
|
|
||||||
|
-- create/drop database for pg >= 16
|
||||||
|
|
||||||
|
set citus.enable_create_database_propagation=on;
|
||||||
|
|
||||||
|
-- test icu_rules
|
||||||
|
--
|
||||||
|
-- practically we don't support it but better to test
|
||||||
|
|
||||||
|
CREATE DATABASE citus_icu_rules_test WITH icu_rules='de_DE@collation=phonebook';
|
||||||
|
CREATE DATABASE citus_icu_rules_test WITH icu_rules='de_DE@collation=phonebook' locale_provider='icu';
|
||||||
|
CREATE DATABASE citus_icu_rules_test WITH icu_rules='de_DE@collation=phonebook' locale_provider='icu' icu_locale = 'de_DE';
|
|
@ -581,3 +581,74 @@ BEGIN
|
||||||
ORDER BY node_type;
|
ORDER BY node_type;
|
||||||
END;
|
END;
|
||||||
$func$ LANGUAGE plpgsql;
|
$func$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- For all nodes, returns database properties of given database, except
|
||||||
|
-- oid, datfrozenxid and datminmxid.
|
||||||
|
--
|
||||||
|
-- Also returns whether the node has a pg_dist_object record for the database
|
||||||
|
-- and whether there are any stale pg_dist_object records for a database.
|
||||||
|
CREATE OR REPLACE FUNCTION check_database_on_all_nodes(p_database_name text)
|
||||||
|
RETURNS TABLE (node_type text, result text)
|
||||||
|
AS $func$
|
||||||
|
DECLARE
|
||||||
|
pg_ge_15_options text := '';
|
||||||
|
pg_ge_16_options text := '';
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_database'::regclass AND attname = 'datlocprovider') THEN
|
||||||
|
pg_ge_15_options := ', daticulocale, datcollversion, datlocprovider';
|
||||||
|
ELSE
|
||||||
|
pg_ge_15_options := $$, null as daticulocale, null as datcollversion, 'c' as datlocprovider$$;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'pg_database'::regclass AND attname = 'daticurules') THEN
|
||||||
|
pg_ge_16_options := ', daticurules';
|
||||||
|
ELSE
|
||||||
|
pg_ge_16_options := ', null as daticurules';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
CASE WHEN (groupid = 0 AND groupid = (SELECT groupid FROM pg_dist_local_group)) THEN 'coordinator (local)'
|
||||||
|
WHEN (groupid = 0) THEN 'coordinator (remote)'
|
||||||
|
WHEN (groupid = (SELECT groupid FROM pg_dist_local_group)) THEN 'worker node (local)'
|
||||||
|
ELSE 'worker node (remote)'
|
||||||
|
END AS node_type,
|
||||||
|
q2.result
|
||||||
|
FROM run_command_on_all_nodes(
|
||||||
|
format(
|
||||||
|
$$
|
||||||
|
SELECT to_jsonb(q.*)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
SELECT to_jsonb(database_properties.*)
|
||||||
|
FROM (
|
||||||
|
SELECT datname, pa.rolname as database_owner,
|
||||||
|
pg_encoding_to_char(pd.encoding) as encoding,
|
||||||
|
datistemplate, datallowconn, datconnlimit, datacl,
|
||||||
|
pt.spcname AS tablespace, datcollate, datctype
|
||||||
|
%2$s -- >= pg15 options
|
||||||
|
%3$s -- >= pg16 options
|
||||||
|
FROM pg_database pd
|
||||||
|
JOIN pg_authid pa ON pd.datdba = pa.oid
|
||||||
|
JOIN pg_tablespace pt ON pd.dattablespace = pt.oid
|
||||||
|
WHERE datname = '%1$s'
|
||||||
|
) database_properties
|
||||||
|
) AS database_properties,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)=1
|
||||||
|
FROM pg_dist_object WHERE objid = (SELECT oid FROM pg_database WHERE datname = '%1$s')
|
||||||
|
) AS pg_dist_object_record_for_db_exists,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) > 0
|
||||||
|
FROM pg_dist_object
|
||||||
|
WHERE classid = 1262 AND objid NOT IN (SELECT oid FROM pg_database)
|
||||||
|
) AS stale_pg_dist_object_record_for_a_db_exists
|
||||||
|
) q
|
||||||
|
$$,
|
||||||
|
p_database_name, pg_ge_15_options, pg_ge_16_options
|
||||||
|
)
|
||||||
|
) q2
|
||||||
|
JOIN pg_dist_node USING (nodeid);
|
||||||
|
END;
|
||||||
|
$func$ LANGUAGE plpgsql;
|
||||||
|
|
Loading…
Reference in New Issue