diff --git a/CHANGELOG.md b/CHANGELOG.md index 50a9c57e2..94bafe5d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +### citus v11.0.1_beta (April 11, 2022) ### + +* Adds propagation of `DOMAIN` objects + +* Adds support for `TABLESAMPLE` + +* Allows adding a unique constraint with an index + +* Fixes a bug that could cause `EXPLAIN ANALYZE` to fail for prepared statements + with custom type + +* Fixes a bug that could cause Citus not to create function in transaction block + properly + +* Fixes a bug that could cause returning invalid JSON when running + `EXPLAIN ANALYZE` with subplans + +* Fixes a bug that prevents non-client backends from accessing shards + ### citus v11.0.0_beta (March 22, 2022) ### * Drops support for PostgreSQL 12 diff --git a/src/backend/distributed/commands/dependencies.c b/src/backend/distributed/commands/dependencies.c index 5e941bf1b..de747d6d7 100644 --- a/src/backend/distributed/commands/dependencies.c +++ b/src/backend/distributed/commands/dependencies.c @@ -363,6 +363,15 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency) return CreateCollationDDLsIdempotent(dependency->objectId); } + case OCLASS_CONSTRAINT: + { + /* + * Constraints can only be reached by domains, they resolve functions. + * Constraints themself are recreated by the domain recreation. + */ + return NIL; + } + case OCLASS_DATABASE: { List *databaseDDLCommands = NIL; diff --git a/src/backend/distributed/commands/distribute_object_ops.c b/src/backend/distributed/commands/distribute_object_ops.c index f84a177d9..d4ed6b90f 100644 --- a/src/backend/distributed/commands/distribute_object_ops.c +++ b/src/backend/distributed/commands/distribute_object_ops.c @@ -153,6 +153,14 @@ static DistributeObjectOps Any_CompositeType = { .address = CompositeTypeStmtObjectAddress, .markDistributed = true, }; +static DistributeObjectOps Any_CreateDomain = { + .deparse = DeparseCreateDomainStmt, + .qualify = QualifyCreateDomainStmt, + .preprocess = PreprocessCreateDomainStmt, + .postprocess = PostprocessCreateDomainStmt, + .address = CreateDomainStmtObjectAddress, + .markDistributed = true, +}; static DistributeObjectOps Any_CreateEnum = { .deparse = DeparseCreateEnumStmt, .qualify = QualifyCreateEnumStmt, @@ -313,6 +321,55 @@ static DistributeObjectOps Database_AlterOwner = { .address = AlterDatabaseOwnerObjectAddress, .markDistributed = false, }; +static DistributeObjectOps Domain_Alter = { + .deparse = DeparseAlterDomainStmt, + .qualify = QualifyAlterDomainStmt, + .preprocess = PreprocessAlterDomainStmt, + .postprocess = PostprocessAlterDomainStmt, + .address = AlterDomainStmtObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps Domain_AlterObjectSchema = { + .deparse = DeparseAlterDomainSchemaStmt, + .qualify = QualifyAlterDomainSchemaStmt, + .preprocess = PreprocessAlterDomainSchemaStmt, + .postprocess = PostprocessAlterDomainSchemaStmt, + .address = AlterTypeSchemaStmtObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps Domain_AlterOwner = { + .deparse = DeparseAlterDomainOwnerStmt, + .qualify = QualifyAlterDomainOwnerStmt, + .preprocess = PreprocessAlterDomainOwnerStmt, + .postprocess = PostprocessAlterDomainOwnerStmt, + .address = AlterDomainOwnerStmtObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps Domain_Drop = { + .deparse = DeparseDropDomainStmt, + .qualify = QualifyDropDomainStmt, + .preprocess = PreprocessDropDomainStmt, + .postprocess = NULL, + .address = NULL, + .markDistributed = false, +}; +static DistributeObjectOps Domain_Rename = { + .deparse = DeparseRenameDomainStmt, + .qualify = QualifyRenameDomainStmt, + .preprocess = PreprocessRenameDomainStmt, + .postprocess = NULL, + .address = RenameDomainStmtObjectAddress, + .markDistributed = false, +}; + +static DistributeObjectOps Domain_RenameConstraint = { + .deparse = DeparseDomainRenameConstraintStmt, + .qualify = QualifyDomainRenameConstraintStmt, + .preprocess = PreprocessDomainRenameConstraintStmt, + .postprocess = NULL, + .address = DomainRenameConstraintStmtObjectAddress, + .markDistributed = false, +}; static DistributeObjectOps Extension_AlterObjectSchema = { .deparse = DeparseAlterExtensionSchemaStmt, .qualify = NULL, @@ -831,6 +888,11 @@ GetDistributeObjectOps(Node *node) { switch (nodeTag(node)) { + case T_AlterDomainStmt: + { + return &Domain_Alter; + } + case T_AlterEnumStmt: { return &Any_AlterEnum; @@ -903,6 +965,11 @@ GetDistributeObjectOps(Node *node) return &Collation_AlterObjectSchema; } + case OBJECT_DOMAIN: + { + return &Domain_AlterObjectSchema; + } + case OBJECT_EXTENSION: { return &Extension_AlterObjectSchema; @@ -981,6 +1048,11 @@ GetDistributeObjectOps(Node *node) return &Database_AlterOwner; } + case OBJECT_DOMAIN: + { + return &Domain_AlterOwner; + } + case OBJECT_FOREIGN_SERVER: { return &ForeignServer_AlterOwner; @@ -1139,6 +1211,11 @@ GetDistributeObjectOps(Node *node) return &Any_CompositeType; } + case T_CreateDomainStmt: + { + return &Any_CreateDomain; + } + case T_CreateEnumStmt: { return &Any_CreateEnum; @@ -1226,6 +1303,11 @@ GetDistributeObjectOps(Node *node) return &Collation_Drop; } + case OBJECT_DOMAIN: + { + return &Domain_Drop; + } + case OBJECT_EXTENSION: { return &Extension_Drop; @@ -1365,6 +1447,16 @@ GetDistributeObjectOps(Node *node) return &Collation_Rename; } + case OBJECT_DOMAIN: + { + return &Domain_Rename; + } + + case OBJECT_DOMCONSTRAINT: + { + return &Domain_RenameConstraint; + } + case OBJECT_FOREIGN_SERVER: { return &ForeignServer_Rename; diff --git a/src/backend/distributed/commands/domain.c b/src/backend/distributed/commands/domain.c new file mode 100644 index 000000000..7e4d5c080 --- /dev/null +++ b/src/backend/distributed/commands/domain.c @@ -0,0 +1,702 @@ +/*------------------------------------------------------------------------- + * + * domain.c + * Hooks to handle the creation, altering and removal of domains. + * These hooks are responsible for duplicating the changes to the + * workers nodes. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/genam.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "parser/parse_type.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/regproc.h" +#include "utils/syscache.h" + +#include "distributed/commands.h" +#include "distributed/commands/utility_hook.h" +#include "distributed/deparser.h" +#include "distributed/listutils.h" +#include "distributed/metadata/distobject.h" +#include "distributed/metadata_sync.h" +#include "distributed/metadata_utility.h" +#include "distributed/multi_executor.h" +#include "distributed/worker_create_or_replace.h" +#include "distributed/worker_transaction.h" + + +static CollateClause * MakeCollateClauseFromOid(Oid collationOid); +static List * FilterNameListForDistributedDomains(List *domainNames, bool missing_ok, + List **distributedDomainAddresses); +static ObjectAddress GetDomainAddressByName(TypeName *domainName, bool missing_ok); + +/* + * PreprocessCreateDomainStmt handles the propagation of the create domain statements. + */ +List * +PreprocessCreateDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + if (!ShouldPropagate()) + { + return NIL; + } + + /* check creation against multi-statement transaction policy */ + if (!ShouldPropagateCreateInCoordinatedTransction()) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + QualifyTreeNode(node); + const char *sql = DeparseTreeNode(node); + sql = WrapCreateOrReplace(sql); + + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sql, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PostprocessCreateDomainStmt gets called after the domain has been created locally. When + * the domain is decided to be propagated we make sure all the domains dependencies exist + * on all workers. + */ +List * +PostprocessCreateDomainStmt(Node *node, const char *queryString) +{ + if (!ShouldPropagate()) + { + return NIL; + } + + /* check creation against multi-statement transaction policy */ + if (!ShouldPropagateCreateInCoordinatedTransction()) + { + return NIL; + } + + /* + * find object address of the just created object, because the domain has been created + * locally it can't be missing + */ + ObjectAddress typeAddress = GetObjectAddressFromParseTree(node, false); + EnsureDependenciesExistOnAllNodes(&typeAddress); + + return NIL; +} + + +/* + * PreprocessDropDomainStmt gets called before dropping the domain locally. For + * distributed domains it will make sure the fully qualified statement is forwarded to all + * the workers reflecting the drop of the domain. + */ +List * +PreprocessDropDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + DropStmt *stmt = castNode(DropStmt, node); + + + if (!ShouldPropagate()) + { + return NIL; + } + + QualifyTreeNode((Node *) stmt); + + List *oldDomains = stmt->objects; + List *distributedDomainAddresses = NIL; + List *distributedDomains = FilterNameListForDistributedDomains( + oldDomains, + stmt->missing_ok, + &distributedDomainAddresses); + if (list_length(distributedDomains) <= 0) + { + /* no distributed domains to drop */ + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + ObjectAddress *addressItem = NULL; + foreach_ptr(addressItem, distributedDomainAddresses) + { + UnmarkObjectDistributed(addressItem); + } + + /* + * temporary swap the lists of objects to delete with the distributed objects and + * deparse to an executable sql statement for the workers + */ + stmt->objects = distributedDomains; + char *dropStmtSql = DeparseTreeNode((Node *) stmt); + stmt->objects = oldDomains; + + /* to prevent recursion with mx we disable ddl propagation */ + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) dropStmtSql, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PreprocessAlterDomainStmt gets called for all domain specific alter statements. When + * the change happens on a distributed domain we reflect the changes on the workers. + */ +List * +PreprocessAlterDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + AlterDomainStmt *stmt = castNode(AlterDomainStmt, node); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + QualifyTreeNode((Node *) stmt); + char *sqlStmt = DeparseTreeNode((Node *) stmt); + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sqlStmt, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PostprocessAlterDomainStmt gets called after the domain has been altered locally. A + * change on the constraints could cause new (undistributed) objects to be dependencies of + * the domain. Here we recreate any new dependencies on the workers before we forward the + * alter statement to the workers. + */ +List * +PostprocessAlterDomainStmt(Node *node, const char *queryString) +{ + AlterDomainStmt *stmt = castNode(AlterDomainStmt, node); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureDependenciesExistOnAllNodes(&domainAddress); + return NIL; +} + + +/* + * PreprocessDomainRenameConstraintStmt gets called locally when a constraint on a domain + * is renamed. When the constraint is on a distributed domain we forward the statement + * appropriately. + */ +List * +PreprocessDomainRenameConstraintStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_DOMCONSTRAINT); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + QualifyTreeNode((Node *) stmt); + char *sqlStmt = DeparseTreeNode((Node *) stmt); + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sqlStmt, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PreprocessAlterDomainOwnerStmt called locally when the owner of a constraint is + * changed. For distributed domains the statement is forwarded to all the workers. + */ +List * +PreprocessAlterDomainOwnerStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + Assert(stmt->objectType == OBJECT_DOMAIN); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + QualifyTreeNode((Node *) stmt); + char *sqlStmt = DeparseTreeNode((Node *) stmt); + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sqlStmt, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PostprocessAlterDomainOwnerStmt given the change of ownership could cause new + * dependencies to exist for the domain we make sure all dependencies for the domain are + * created before we forward the statement to the workers. + */ +List * +PostprocessAlterDomainOwnerStmt(Node *node, const char *queryString) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureDependenciesExistOnAllNodes(&domainAddress); + return NIL; +} + + +/* + * PreprocessRenameDomainStmt creates the statements to execute on the workers when the + * domain being renamed is distributed. + */ +List * +PreprocessRenameDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_DOMAIN); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + QualifyTreeNode((Node *) stmt); + char *sqlStmt = DeparseTreeNode((Node *) stmt); + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sqlStmt, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PreprocessAlterDomainSchemaStmt cretes the statements to execute on the workers when + * the domain being moved to a new schema has been distributed. + */ +List * +PreprocessAlterDomainSchemaStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + Assert(stmt->objectType == OBJECT_DOMAIN); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureCoordinator(); + EnsureSequentialMode(OBJECT_DOMAIN); + + QualifyTreeNode((Node *) stmt); + char *sqlStmt = DeparseTreeNode((Node *) stmt); + List *commands = list_make3(DISABLE_DDL_PROPAGATION, + (void *) sqlStmt, + ENABLE_DDL_PROPAGATION); + + return NodeDDLTaskList(NON_COORDINATOR_NODES, commands); +} + + +/* + * PostprocessAlterDomainSchemaStmt makes sure any new dependencies (as result of the + * schema move) are created on the workers before we forward the statement. + */ +List * +PostprocessAlterDomainSchemaStmt(Node *node, const char *queryString) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + + ObjectAddress domainAddress = GetObjectAddressFromParseTree((Node *) stmt, false); + if (!ShouldPropagateObject(&domainAddress)) + { + return NIL; + } + + EnsureDependenciesExistOnAllNodes(&domainAddress); + return NIL; +} + + +/* + * FilterNameListForDistributedDomains given a liost of domain names we will return a list + * filtered with only the names of distributed domains remaining. The pointer to the list + * distributedDomainAddresses is populated with a list of ObjectAddresses of the domains + * that are distributed. Indices between the returned list and the object addresses are + * synchronizes. + * Note: the pointer in distributedDomainAddresses should not be omitted + * + * When missing_ok is false this function will raise an error if a domain identified by a + * domain name cannot be found. + */ +static List * +FilterNameListForDistributedDomains(List *domainNames, bool missing_ok, + List **distributedDomainAddresses) +{ + Assert(distributedDomainAddresses != NULL); + + List *distributedDomainNames = NIL; + TypeName *domainName = NULL; + foreach_ptr(domainName, domainNames) + { + ObjectAddress objectAddress = GetDomainAddressByName(domainName, missing_ok); + if (IsObjectDistributed(&objectAddress)) + { + distributedDomainNames = lappend(distributedDomainNames, domainName); + if (distributedDomainAddresses) + { + ObjectAddress *allocatedAddress = palloc0(sizeof(ObjectAddress)); + *allocatedAddress = objectAddress; + *distributedDomainAddresses = lappend(*distributedDomainAddresses, + allocatedAddress); + } + } + } + + return distributedDomainNames; +} + + +/* + * GetDomainAddressByName returns the ObjectAddress of the domain identified by + * domainName. When missing_ok is true the object id part of the ObjectAddress can be + * InvalidOid. When missing_ok is false this function will raise an error instead when the + * domain can't be found. + */ +static ObjectAddress +GetDomainAddressByName(TypeName *domainName, bool missing_ok) +{ + ObjectAddress address = { 0 }; + Oid domainOid = LookupTypeNameOid(NULL, domainName, missing_ok); + ObjectAddressSet(address, TypeRelationId, domainOid); + return address; +} + + +/* + * RecreateDomainStmt returns a CreateDomainStmt pointer where the statement represents + * the creation of the domain to recreate the domain on a different postgres node based on + * the current representation in the local catalog. + */ +CreateDomainStmt * +RecreateDomainStmt(Oid domainOid) +{ + CreateDomainStmt *stmt = makeNode(CreateDomainStmt); + stmt->domainname = stringToQualifiedNameList(format_type_be_qualified(domainOid)); + + HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainOid)); + if (!HeapTupleIsValid(tup)) + { + elog(ERROR, "cache lookup failed for type %u", domainOid); + } + + Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); + if (typTup->typtype != TYPTYPE_DOMAIN) + { + elog(ERROR, "type is not a domain type"); + } + + stmt->typeName = makeTypeNameFromOid(typTup->typbasetype, typTup->typtypmod); + + if (OidIsValid(typTup->typcollation)) + { + stmt->collClause = MakeCollateClauseFromOid(typTup->typcollation); + } + + /* + * typdefault and typdefaultbin are potentially null, so don't try to + * access 'em as struct fields. Must do it the hard way with + * SysCacheGetAttr. + */ + bool isNull = false; + Datum typeDefaultDatum = SysCacheGetAttr(TYPEOID, + tup, + Anum_pg_type_typdefaultbin, + &isNull); + if (!isNull) + { + /* when not null there is default value which we should add as a constraint */ + Constraint *constraint = makeNode(Constraint); + constraint->contype = CONSTR_DEFAULT; + constraint->cooked_expr = TextDatumGetCString(typeDefaultDatum); + + stmt->constraints = lappend(stmt->constraints, constraint); + } + + /* NOT NULL constraints are non-named on the actual type */ + if (typTup->typnotnull) + { + Constraint *constraint = makeNode(Constraint); + constraint->contype = CONSTR_NOTNULL; + + stmt->constraints = lappend(stmt->constraints, constraint); + } + + /* lookup and look all constraints to add them to the CreateDomainStmt */ + Relation conRel = table_open(ConstraintRelationId, AccessShareLock); + + /* Look for CHECK Constraints on this domain */ + ScanKeyData key[1]; + ScanKeyInit(&key[0], + Anum_pg_constraint_contypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(domainOid)); + + SysScanDesc scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, NULL, 1, + key); + + HeapTuple conTup = NULL; + while (HeapTupleIsValid(conTup = systable_getnext(scan))) + { + Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup); + + if (c->contype != CONSTRAINT_CHECK) + { + /* Ignore non-CHECK constraints, shouldn't be any */ + continue; + } + + /* + * We create a constraint, completely ignoring c->convalidated because we can't + * create a domain with an invalidated constraint. Once a constraint is added to + * a domain -even non valid-, all new data is validated. Meaning, creating a + * domain with a non-valid constraint doesn't make any sense. + * + * Given it will be too hard to defer the creation of a constraint till we + * validate the constraint on the coordinator we will simply create the + * non-validated constraint to ad hear to validating all new data. + * + * An edgecase here would be when moving existing data, that hasn't been validated + * before to an other node. This behaviour is consistent with sending it to an + * already existing node (that has the constraint created but not validated) and a + * new node. + */ + + Constraint *constraint = makeNode(Constraint); + + constraint->conname = pstrdup(NameStr(c->conname)); + constraint->contype = CONSTR_CHECK; /* we only come here with check constraints */ + + /* Not expecting conbin to be NULL, but we'll test for it anyway */ + Datum conbin = heap_getattr(conTup, Anum_pg_constraint_conbin, conRel->rd_att, + &isNull); + if (isNull) + { + elog(ERROR, "domain \"%s\" constraint \"%s\" has NULL conbin", + NameStr(typTup->typname), NameStr(c->conname)); + } + + /* + * The conbin containes the cooked expression from when the constraint was + * inserted into the catalog. We store it here for the deparser to distinguish + * between cooked expressions and raw expressions. + * + * There is no supported way to go from a cooked expression to a raw expression. + */ + constraint->cooked_expr = TextDatumGetCString(conbin); + + stmt->constraints = lappend(stmt->constraints, constraint); + } + + systable_endscan(scan); + table_close(conRel, NoLock); + + ReleaseSysCache(tup); + + QualifyTreeNode((Node *) stmt); + + return stmt; +} + + +/* + * MakeCollateClauseFromOid returns a CollateClause describing the COLLATE segment of a + * CREATE DOMAIN statement based on the Oid of the collation used for the domain. + */ +static CollateClause * +MakeCollateClauseFromOid(Oid collationOid) +{ + CollateClause *collateClause = makeNode(CollateClause); + + ObjectAddress collateAddress = { 0 }; + ObjectAddressSet(collateAddress, CollationRelationId, collationOid); + + List *objName = NIL; + List *objArgs = NIL; + + #if PG_VERSION_NUM >= PG_VERSION_14 + getObjectIdentityParts(&collateAddress, &objName, &objArgs, false); + #else + getObjectIdentityParts(&collateAddress, &objName, &objArgs); + #endif + + char *name = NULL; + foreach_ptr(name, objName) + { + collateClause->collname = lappend(collateClause->collname, makeString(name)); + } + + collateClause->location = -1; + + return collateClause; +} + + +/* + * CreateDomainStmtObjectAddress returns the ObjectAddress of the domain that would be + * created by the statement. When missing_ok is false the function will raise an error if + * the domain cannot be found in the local catalog. + */ +ObjectAddress +CreateDomainStmtObjectAddress(Node *node, bool missing_ok) +{ + CreateDomainStmt *stmt = castNode(CreateDomainStmt, node); + + TypeName *typeName = makeTypeNameFromNameList(stmt->domainname); + Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok); + ObjectAddress address = { 0 }; + ObjectAddressSet(address, TypeRelationId, typeOid); + + return address; +} + + +/* + * AlterDomainStmtObjectAddress returns the ObjectAddress of the domain being altered. + * When missing_ok is false this function will raise an error when the domain is not + * found. + */ +ObjectAddress +AlterDomainStmtObjectAddress(Node *node, bool missing_ok) +{ + AlterDomainStmt *stmt = castNode(AlterDomainStmt, node); + + TypeName *domainName = makeTypeNameFromNameList(stmt->typeName); + return GetDomainAddressByName(domainName, missing_ok); +} + + +/* + * DomainRenameConstraintStmtObjectAddress returns the ObjectAddress of the domain for + * which the constraint is being renamed. When missing_ok this function will raise an + * error if the domain cannot be found. + */ +ObjectAddress +DomainRenameConstraintStmtObjectAddress(Node *node, bool missing_ok) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + + TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object)); + return GetDomainAddressByName(domainName, missing_ok); +} + + +/* + * AlterDomainOwnerStmtObjectAddress returns the ObjectAddress for which the owner is + * being changed. When missing_ok is false this function will raise an error if the domain + * cannot be found. + */ +ObjectAddress +AlterDomainOwnerStmtObjectAddress(Node *node, bool missing_ok) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + Assert(stmt->objectType == OBJECT_DOMAIN); + + TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object)); + return GetDomainAddressByName(domainName, missing_ok); +} + + +/* + * RenameDomainStmtObjectAddress returns the ObjectAddress of the domain being renamed. + * When missing_ok is false this function will raise an error when the domain cannot be + * found. + */ +ObjectAddress +RenameDomainStmtObjectAddress(Node *node, bool missing_ok) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_DOMAIN); + + TypeName *domainName = makeTypeNameFromNameList(castNode(List, stmt->object)); + return GetDomainAddressByName(domainName, missing_ok); +} + + +/* + * get_constraint_typid returns the contypid of a constraint. This field is only set for + * constraints on domain types. Returns InvalidOid if conoid is an invalid constraint, as + * well as for constraints that are not on domain types. + */ +Oid +get_constraint_typid(Oid conoid) +{ + HeapTuple tp = SearchSysCache1(CONSTROID, ObjectIdGetDatum(conoid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp); + Oid result = contup->contypid; + ReleaseSysCache(tp); + return result; + } + else + { + return InvalidOid; + } +} diff --git a/src/backend/distributed/commands/function.c b/src/backend/distributed/commands/function.c index 757c24fed..879aa4770 100644 --- a/src/backend/distributed/commands/function.c +++ b/src/backend/distributed/commands/function.c @@ -1263,12 +1263,7 @@ ShouldPropagateCreateFunction(CreateFunctionStmt *stmt) return false; } - /* - * If the create command is a part of a multi-statement transaction that is not in - * sequential mode, don't propagate. - */ - if (IsMultiStatementTransaction() && - MultiShardConnectionType != SEQUENTIAL_CONNECTION) + if (!ShouldPropagateCreateInCoordinatedTransction()) { return false; } diff --git a/src/backend/distributed/commands/type.c b/src/backend/distributed/commands/type.c index 74718ea59..4973aafd0 100644 --- a/src/backend/distributed/commands/type.c +++ b/src/backend/distributed/commands/type.c @@ -612,6 +612,11 @@ CreateTypeStmtByObjectAddress(const ObjectAddress *address) return (Node *) RecreateCompositeTypeStmt(address->objectId); } + case TYPTYPE_DOMAIN: + { + return (Node *) RecreateDomainStmt(address->objectId); + } + default: { ereport(ERROR, (errmsg("unsupported type to generate create statement for"), @@ -854,7 +859,7 @@ ObjectAddress AlterTypeSchemaStmtObjectAddress(Node *node, bool missing_ok) { AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); - Assert(stmt->objectType == OBJECT_TYPE); + Assert(stmt->objectType == OBJECT_TYPE || stmt->objectType == OBJECT_DOMAIN); List *names = (List *) stmt->object; diff --git a/src/backend/distributed/commands/view.c b/src/backend/distributed/commands/view.c index e55b7f6bf..e9e7407e3 100644 --- a/src/backend/distributed/commands/view.c +++ b/src/backend/distributed/commands/view.c @@ -157,7 +157,6 @@ PreprocessDropViewStmt(Node *node, const char *queryString, ProcessUtilityContex processUtilityContext) { DropStmt *stmt = castNode(DropStmt, node); - List *distributedViews = NIL; if (!ShouldPropagate()) { @@ -170,7 +169,8 @@ PreprocessDropViewStmt(Node *node, const char *queryString, ProcessUtilityContex */ QualifyTreeNode((Node *) stmt); - distributedViews = FilterNameListForDistributedViews(stmt->objects, stmt->missing_ok); + List *distributedViews = FilterNameListForDistributedViews(stmt->objects, + stmt->missing_ok); if (list_length(distributedViews) < 1) { diff --git a/src/backend/distributed/deparser/deparse_domain_stmts.c b/src/backend/distributed/deparser/deparse_domain_stmts.c new file mode 100644 index 000000000..9891e0532 --- /dev/null +++ b/src/backend/distributed/deparser/deparse_domain_stmts.c @@ -0,0 +1,626 @@ +/*------------------------------------------------------------------------- + * + * deparse_domain_stmts.c + * Functions to turn all Statement structures related to domains back + * into sql. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/heap.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/parsenodes.h" +#include "parser/parse_coerce.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_node.h" +#include "parser/parse_type.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/ruleutils.h" +#include "utils/syscache.h" + +#include "distributed/citus_ruleutils.h" +#include "distributed/deparser.h" +#include "distributed/listutils.h" +#include "distributed/namespace_utils.h" + +static void AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName, + TypeName *typeName); +static Node * replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref); +static Node * TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName); +static Node * TransformConstraintExpr(Node *expr, TypeName *typeName); +static CoerceToDomainValue * GetCoerceDomainValue(TypeName *typeName); +static char * TypeNameAsIdentifier(TypeName *typeName); + +static Oid DomainGetBaseTypeOid(List *names, int32 *baseTypeMod); + +static void AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt); +static void AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt); +static void AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt); + + +/* + * DeparseCreateDomainStmt returns the sql representation for the CREATE DOMAIN statement. + */ +char * +DeparseCreateDomainStmt(Node *node) +{ + CreateDomainStmt *stmt = castNode(CreateDomainStmt, node); + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + const char *domainIdentifier = NameListToQuotedString(stmt->domainname); + const char *typeIdentifier = TypeNameAsIdentifier(stmt->typeName); + appendStringInfo(&buf, "CREATE DOMAIN %s AS %s", domainIdentifier, typeIdentifier); + + if (stmt->collClause) + { + const char *collateIdentifier = + NameListToQuotedString(stmt->collClause->collname); + appendStringInfo(&buf, " COLLATE %s", collateIdentifier); + } + + Constraint *constraint = NULL; + foreach_ptr(constraint, stmt->constraints) + { + AppendConstraint(&buf, constraint, stmt->domainname, stmt->typeName); + } + + appendStringInfoString(&buf, ";"); + + return buf.data; +} + + +/* + * TypeNameAsIdentifier returns the sql identifier of a TypeName. This is more complex + * than concatenating the schema name and typename since certain types contain modifiers + * that need to be correctly represented. + */ +static char * +TypeNameAsIdentifier(TypeName *typeName) +{ + int32 typmod = 0; + Oid typeOid = InvalidOid; + bits16 formatFlags = FORMAT_TYPE_TYPEMOD_GIVEN | FORMAT_TYPE_FORCE_QUALIFY; + + typenameTypeIdAndMod(NULL, typeName, &typeOid, &typmod); + + return format_type_extended(typeOid, typmod, formatFlags); +} + + +/* + * DeparseDropDomainStmt returns the sql for teh DROP DOMAIN statement. + */ +char * +DeparseDropDomainStmt(Node *node) +{ + DropStmt *stmt = castNode(DropStmt, node); + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + appendStringInfoString(&buf, "DROP DOMAIN "); + if (stmt->missing_ok) + { + appendStringInfoString(&buf, "IF EXISTS "); + } + + TypeName *domainName = NULL; + bool first = true; + foreach_ptr(domainName, stmt->objects) + { + if (!first) + { + appendStringInfoString(&buf, ", "); + } + first = false; + + const char *identifier = NameListToQuotedString(domainName->names); + appendStringInfoString(&buf, identifier); + } + + if (stmt->behavior == DROP_CASCADE) + { + appendStringInfoString(&buf, " CASCADE"); + } + + appendStringInfoString(&buf, ";"); + + return buf.data; +} + + +/* + * DeparseAlterDomainStmt returns the sql representation of the DOMAIN specific ALTER + * statements. + */ +char * +DeparseAlterDomainStmt(Node *node) +{ + AlterDomainStmt *stmt = castNode(AlterDomainStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + appendStringInfo(&buf, "ALTER DOMAIN %s ", NameListToQuotedString(stmt->typeName)); + switch (stmt->subtype) + { + case 'T': /* SET DEFAULT */ + { + AppendAlterDomainStmtSetDefault(&buf, stmt); + break; + } + + case 'N': /* DROP NOT NULL */ + { + appendStringInfoString(&buf, "DROP NOT NULL"); + break; + } + + case 'O': /* SET NOT NULL */ + { + appendStringInfoString(&buf, "SET NOT NULL"); + break; + } + + case 'C': /* ADD [CONSTRAINT name] */ + { + AppendAlterDomainStmtAddConstraint(&buf, stmt); + break; + } + + case 'X': /* DROP CONSTRAINT */ + { + AppendAlterDomainStmtDropConstraint(&buf, stmt); + break; + } + + case 'V': /* VALIDATE CONSTRAINT */ + { + appendStringInfo(&buf, "VALIDATE CONSTRAINT %s", + quote_identifier(stmt->name)); + break; + } + + default: + { + elog(ERROR, "unsupported alter domain statement for distribution"); + } + } + + appendStringInfoChar(&buf, ';'); + + return buf.data; +} + + +/* + * DeparseDomainRenameConstraintStmt returns the sql representation of the domain + * constraint renaming. + */ +char * +DeparseDomainRenameConstraintStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + char *domainIdentifier = NameListToQuotedString(castNode(List, stmt->object)); + appendStringInfo(&buf, "ALTER DOMAIN %s RENAME CONSTRAINT %s TO %s;", + domainIdentifier, + quote_identifier(stmt->subname), + quote_identifier(stmt->newname)); + + return buf.data; +} + + +/* + * DeparseAlterDomainOwnerStmt returns the sql representation of the ALTER DOMAIN OWNER + * statement. + */ +char * +DeparseAlterDomainOwnerStmt(Node *node) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + List *domainName = castNode(List, stmt->object); + char *domainIdentifier = NameListToQuotedString(domainName); + appendStringInfo(&buf, "ALTER DOMAIN %s OWNER TO %s;", + domainIdentifier, + RoleSpecString(stmt->newowner, true)); + + return buf.data; +} + + +/* + * DeparseRenameDomainStmt returns the sql representation of the ALTER DOMAIN RENAME + * statement. + */ +char * +DeparseRenameDomainStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + List *domainName = castNode(List, stmt->object); + char *domainIdentifier = NameListToQuotedString(domainName); + appendStringInfo(&buf, "ALTER DOMAIN %s RENAME TO %s;", + domainIdentifier, + quote_identifier(stmt->newname)); + + return buf.data; +} + + +/* + * DeparseAlterDomainSchemaStmt returns the sql representation of the ALTER DOMAIN SET + * SCHEMA statement. + */ +char * +DeparseAlterDomainSchemaStmt(Node *node) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + List *domainName = castNode(List, stmt->object); + char *domainIdentifier = NameListToQuotedString(domainName); + appendStringInfo(&buf, "ALTER DOMAIN %s SET SCHEMA %s;", + domainIdentifier, + quote_identifier(stmt->newschema)); + + return buf.data; +} + + +/* + * DomainGetBaseTypeOid returns the type Oid and the type modifiers of the type underlying + * a domain addresses by the namelist provided as the names argument. The type modifier is + * only provided if the baseTypeMod pointer is a valid pointer on where to write the + * modifier (not a NULL pointer). + * + * If the type cannot be found this function will raise a non-userfacing error. Care needs + * to be taken by the caller that the domain is actually existing. + */ +static Oid +DomainGetBaseTypeOid(List *names, int32 *baseTypeMod) +{ + TypeName *domainName = makeTypeNameFromNameList(names); + Oid domainoid = typenameTypeId(NULL, domainName); + HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domainoid)); + if (!HeapTupleIsValid(tup)) + { + elog(ERROR, "cache lookup failed for type %u", domainoid); + } + Form_pg_type typTup = (Form_pg_type) GETSTRUCT(tup); + Oid baseTypeOid = typTup->typbasetype; + if (baseTypeMod) + { + *baseTypeMod = typTup->typtypmod; + } + ReleaseSysCache(tup); + return baseTypeOid; +} + + +/* + * AppendAlterDomainStmtSetDefault is a helper function that appends the default value + * portion of an ALTER DOMAIN statement that is changing the default value of the domain. + */ +static void +AppendAlterDomainStmtSetDefault(StringInfo buf, AlterDomainStmt *stmt) +{ + if (stmt->def == NULL) + { + /* no default expression is a DROP DEFAULT statment */ + appendStringInfoString(buf, "DROP DEFAULT"); + return; + } + + int32 baseTypMod = 0; + Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod); + TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod); + + /* cook the default expression, without cooking we can't deparse */ + Node *expr = stmt->def; + expr = TransformDefaultExpr(expr, stmt->typeName, baseTypeName); + + /* deparse while the searchpath is cleared to force qualification of identifiers */ + PushOverrideEmptySearchPath(CurrentMemoryContext); + char *exprSql = deparse_expression(expr, NIL, true, true); + PopOverrideSearchPath(); + + appendStringInfo(buf, "SET DEFAULT %s", exprSql); +} + + +/* + * AppendAlterDomainStmtAddConstraint is a helper function that appends the constraint + * specification for an ALTER DOMAIN statement that adds a constraint to the domain. + */ +static void +AppendAlterDomainStmtAddConstraint(StringInfo buf, AlterDomainStmt *stmt) +{ + if (stmt->def == NULL || !IsA(stmt->def, Constraint)) + { + ereport(ERROR, (errmsg("unable to deparse ALTER DOMAIN statement due to " + "unexpected contents"))); + } + + Constraint *constraint = castNode(Constraint, stmt->def); + appendStringInfoString(buf, "ADD"); + + int32 baseTypMod = 0; + Oid baseOid = DomainGetBaseTypeOid(stmt->typeName, &baseTypMod); + TypeName *baseTypeName = makeTypeNameFromOid(baseOid, baseTypMod); + + AppendConstraint(buf, constraint, stmt->typeName, baseTypeName); + + if (!constraint->initially_valid) + { + appendStringInfoString(buf, " NOT VALID"); + } +} + + +/* + * AppendAlterDomainStmtDropConstraint is a helper function that appends the DROP + * CONSTRAINT part of an ALTER DOMAIN statement for an alter statement that drops a + * constraint. + */ +static void +AppendAlterDomainStmtDropConstraint(StringInfo buf, AlterDomainStmt *stmt) +{ + appendStringInfoString(buf, "DROP CONSTRAINT "); + + if (stmt->missing_ok) + { + appendStringInfoString(buf, "IF EXISTS "); + } + + appendStringInfoString(buf, quote_identifier(stmt->name)); + + if (stmt->behavior == DROP_CASCADE) + { + appendStringInfoString(buf, " CASCADE"); + } +} + + +/* + * AppendConstraint is a helper function that appends a constraint specification to a sql + * string that is adding a constraint. + * + * There are multiple places where a constraint specification is appended to sql strings. + * + * Given the complexities of serializing a constraint they all use this routine. + */ +static void +AppendConstraint(StringInfo buf, Constraint *constraint, List *domainName, + TypeName *typeName) +{ + if (constraint->conname) + { + appendStringInfo(buf, " CONSTRAINT %s", quote_identifier(constraint->conname)); + } + + switch (constraint->contype) + { + case CONSTR_CHECK: + { + Node *expr = NULL; + if (constraint->raw_expr) + { + /* the expression was parsed from sql, still needs to transform */ + expr = TransformConstraintExpr(constraint->raw_expr, typeName); + } + else if (constraint->cooked_expr) + { + /* expression was read from the catalog, no cooking required just parse */ + expr = stringToNode(constraint->cooked_expr); + } + else + { + elog(ERROR, "missing expression for domain constraint"); + } + + PushOverrideEmptySearchPath(CurrentMemoryContext); + char *exprSql = deparse_expression(expr, NIL, true, true); + PopOverrideSearchPath(); + + appendStringInfo(buf, " CHECK (%s)", exprSql); + return; + } + + case CONSTR_DEFAULT: + { + Node *expr = NULL; + if (constraint->raw_expr) + { + /* the expression was parsed from sql, still needs to transform */ + expr = TransformDefaultExpr(constraint->raw_expr, domainName, typeName); + } + else if (constraint->cooked_expr) + { + /* expression was read from the catalog, no cooking required just parse */ + expr = stringToNode(constraint->cooked_expr); + } + else + { + elog(ERROR, "missing expression for domain default"); + } + + PushOverrideEmptySearchPath(CurrentMemoryContext); + char *exprSql = deparse_expression(expr, NIL, true, true); + PopOverrideSearchPath(); + + appendStringInfo(buf, " DEFAULT %s", exprSql); + return; + } + + case CONSTR_NOTNULL: + { + appendStringInfoString(buf, " NOT NULL"); + return; + } + + case CONSTR_NULL: + { + appendStringInfoString(buf, " NULL"); + return; + } + + default: + { + ereport(ERROR, (errmsg("unsupported constraint for distributed domain"))); + } + } +} + + +/* + * TransformDefaultExpr transforms a default expression from the expression passed on the + * AST to a cooked version that postgres uses internally. + * + * Only the cooked version can be easily turned back into a sql string, hence its use in + * the deparser. This is only called for default expressions that don't have a cooked + * variant stored. + */ +static Node * +TransformDefaultExpr(Node *expr, List *domainName, TypeName *typeName) +{ + const char *domainNameStr = NameListToQuotedString(domainName); + int32 basetypeMod = 0; /* capture typeMod during lookup */ + Type tup = typenameType(NULL, typeName, &basetypeMod); + Oid basetypeoid = typeTypeId(tup); + + ReleaseSysCache(tup); + + ParseState *pstate = make_parsestate(NULL); + Node *defaultExpr = cookDefault(pstate, expr, + basetypeoid, + basetypeMod, + domainNameStr, + 0); + + return defaultExpr; +} + + +/* + * TransformConstraintExpr transforms a constraint expression from the expression passed + * on the AST to a cooked version that postgres uses internally. + * + * Only the cooked version can be easily turned back into a sql string, hence its use in + * the deparser. This is only called for default expressions that don't have a cooked + * variant stored. + */ +static Node * +TransformConstraintExpr(Node *expr, TypeName *typeName) +{ + /* + * Convert the A_EXPR in raw_expr into an EXPR + */ + ParseState *pstate = make_parsestate(NULL); + + /* + * Set up a CoerceToDomainValue to represent the occurrence of VALUE in + * the expression. Note that it will appear to have the type of the base + * type, not the domain. This seems correct since within the check + * expression, we should not assume the input value can be considered a + * member of the domain. + */ + + CoerceToDomainValue *domVal = GetCoerceDomainValue(typeName); + + pstate->p_pre_columnref_hook = replace_domain_constraint_value; + pstate->p_ref_hook_state = (void *) domVal; + + expr = transformExpr(pstate, expr, EXPR_KIND_DOMAIN_CHECK); + + /* + * Make sure it yields a boolean result. + */ + expr = coerce_to_boolean(pstate, expr, "CHECK"); + + /* + * Fix up collation information. + */ + assign_expr_collations(pstate, expr); + + return expr; +} + + +/* + * GetCoerceDomainValue creates a stub CoerceToDomainValue struct representing the type + * referenced by the typeName. + */ +static CoerceToDomainValue * +GetCoerceDomainValue(TypeName *typeName) +{ + int32 typMod = 0; /* capture typeMod during lookup */ + Type tup = LookupTypeName(NULL, typeName, &typMod, false); + if (tup == NULL) + { + elog(ERROR, "unable to lookup type information for %s", + NameListToQuotedString(typeName->names)); + } + + CoerceToDomainValue *domVal = makeNode(CoerceToDomainValue); + domVal->typeId = typeTypeId(tup); + domVal->typeMod = typMod; + domVal->collation = typeTypeCollation(tup); + domVal->location = -1; + + ReleaseSysCache(tup); + return domVal; +} + + +/* Parser pre_columnref_hook for domain CHECK constraint parsing */ +static Node * +replace_domain_constraint_value(ParseState *pstate, ColumnRef *cref) +{ + /* + * Check for a reference to "value", and if that's what it is, replace + * with a CoerceToDomainValue as prepared for us by domainAddConstraint. + * (We handle VALUE as a name, not a keyword, to avoid breaking a lot of + * applications that have used VALUE as a column name in the past.) + */ + if (list_length(cref->fields) == 1) + { + Node *field1 = (Node *) linitial(cref->fields); + Assert(IsA(field1, String)); + char *colname = strVal(field1); + + if (strcmp(colname, "value") == 0) + { + CoerceToDomainValue *domVal = copyObject(pstate->p_ref_hook_state); + + /* Propagate location knowledge, if any */ + domVal->location = cref->location; + return (Node *) domVal; + } + } + return NULL; +} diff --git a/src/backend/distributed/deparser/qualify_domain.c b/src/backend/distributed/deparser/qualify_domain.c new file mode 100644 index 000000000..0e359cdcf --- /dev/null +++ b/src/backend/distributed/deparser/qualify_domain.c @@ -0,0 +1,257 @@ +/*------------------------------------------------------------------------- + * + * qualify_domain.c + * Functions to fully qualify, make the statements independent of + * search_path settings, for all domain related statements. This + * mostly consists of adding the schema name to all the domain + * names referencing domains. + * + * Copyright (c) Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/namespace.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_type.h" +#include "nodes/makefuncs.h" +#include "parser/parse_type.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +#include "distributed/deparser.h" +#include "distributed/listutils.h" + + +static void QualifyTypeName(TypeName *typeName, bool missing_ok); +static void QualifyCollate(CollateClause *collClause, bool missing_ok); + + +/* + * QualifyCreateDomainStmt modifies the CreateDomainStmt passed to become search_path + * independent. + */ +void +QualifyCreateDomainStmt(Node *node) +{ + CreateDomainStmt *stmt = castNode(CreateDomainStmt, node); + + char *schemaName = NULL; + char *domainName = NULL; + + /* fully qualify domain name */ + DeconstructQualifiedName(stmt->domainname, &schemaName, &domainName); + if (!schemaName) + { + RangeVar *var = makeRangeVarFromNameList(stmt->domainname); + Oid creationSchema = RangeVarGetCreationNamespace(var); + schemaName = get_namespace_name(creationSchema); + + stmt->domainname = list_make2(makeString(schemaName), makeString(domainName)); + } + + /* referenced types should be fully qualified */ + QualifyTypeName(stmt->typeName, false); + QualifyCollate(stmt->collClause, false); +} + + +/* + * QualifyDropDomainStmt modifies the DropStmt for DOMAIN's to be search_path independent. + */ +void +QualifyDropDomainStmt(Node *node) +{ + DropStmt *stmt = castNode(DropStmt, node); + + TypeName *domainName = NULL; + foreach_ptr(domainName, stmt->objects) + { + QualifyTypeName(domainName, stmt->missing_ok); + } +} + + +/* + * QualifyAlterDomainStmt modifies the AlterDomainStmt to be search_path independent. + */ +void +QualifyAlterDomainStmt(Node *node) +{ + AlterDomainStmt *stmt = castNode(AlterDomainStmt, node); + + if (list_length(stmt->typeName) == 1) + { + TypeName *typeName = makeTypeNameFromNameList(stmt->typeName); + QualifyTypeName(typeName, false); + stmt->typeName = typeName->names; + } +} + + +/* + * QualifyDomainRenameConstraintStmt modifies the RenameStmt for domain constraints to be + * search_path independent. + */ +void +QualifyDomainRenameConstraintStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_DOMCONSTRAINT); + + List *domainName = castNode(List, stmt->object); + if (list_length(domainName) == 1) + { + TypeName *typeName = makeTypeNameFromNameList(domainName); + QualifyTypeName(typeName, false); + stmt->object = (Node *) typeName->names; + } +} + + +/* + * QualifyAlterDomainOwnerStmt modifies the AlterOwnerStmt for DOMAIN's to be search_oath + * independent. + */ +void +QualifyAlterDomainOwnerStmt(Node *node) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + Assert(stmt->objectType == OBJECT_DOMAIN); + + List *domainName = castNode(List, stmt->object); + if (list_length(domainName) == 1) + { + TypeName *typeName = makeTypeNameFromNameList(domainName); + QualifyTypeName(typeName, false); + stmt->object = (Node *) typeName->names; + } +} + + +/* + * QualifyRenameDomainStmt modifies the RenameStmt for the Domain to be search_path + * independent. + */ +void +QualifyRenameDomainStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_DOMAIN); + + List *domainName = castNode(List, stmt->object); + if (list_length(domainName) == 1) + { + TypeName *typeName = makeTypeNameFromNameList(domainName); + QualifyTypeName(typeName, false); + stmt->object = (Node *) typeName->names; + } +} + + +/* + * QualifyAlterDomainSchemaStmt modifies the AlterObjectSchemaStmt to be search_path + * independent. + */ +void +QualifyAlterDomainSchemaStmt(Node *node) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + Assert(stmt->objectType == OBJECT_DOMAIN); + + List *domainName = castNode(List, stmt->object); + if (list_length(domainName) == 1) + { + TypeName *typeName = makeTypeNameFromNameList(domainName); + QualifyTypeName(typeName, false); + stmt->object = (Node *) typeName->names; + } +} + + +/* + * QualifyTypeName qualifies a TypeName object in place. When missing_ok is false it might + * throw an error if the type can't be found based on its name. When an oid is provided + * missing_ok is ignored and treated as false. Meaning, even if missing_ok is true the + * function might raise an error for non-existing types if the oid can't be found. + */ +static void +QualifyTypeName(TypeName *typeName, bool missing_ok) +{ + if (OidIsValid(typeName->typeOid)) + { + /* + * When the typeName is provided as oid, fill in the names. + * missing_ok is ignored for oid's + */ + Type typeTup = typeidType(typeName->typeOid); + + char *name = typeTypeName(typeTup); + + Oid namespaceOid = TypeOidGetNamespaceOid(typeName->typeOid); + char *schemaName = get_namespace_name(namespaceOid); + + typeName->names = list_make2(makeString(schemaName), makeString(name)); + + ReleaseSysCache(typeTup); + } + else + { + char *name = NULL; + char *schemaName = NULL; + DeconstructQualifiedName(typeName->names, &schemaName, &name); + if (!schemaName) + { + Oid typeOid = LookupTypeNameOid(NULL, typeName, missing_ok); + Oid namespaceOid = TypeOidGetNamespaceOid(typeOid); + schemaName = get_namespace_name(namespaceOid); + + typeName->names = list_make2(makeString(schemaName), makeString(name)); + } + } +} + + +/* + * QualifyCollate qualifies any given CollateClause by adding any missing schema name to + * the collation being identified. + * + * If collClause is a NULL pointer this function is a no-nop. + */ +static void +QualifyCollate(CollateClause *collClause, bool missing_ok) +{ + if (collClause == NULL) + { + /* no collate clause, nothing to qualify*/ + return; + } + + if (list_length(collClause->collname) != 1) + { + /* already qualified */ + return; + } + + Oid collOid = get_collation_oid(collClause->collname, missing_ok); + ObjectAddress collationAddress = { 0 }; + ObjectAddressSet(collationAddress, CollationRelationId, collOid); + + List *objName = NIL; + List *objArgs = NIL; + + #if PG_VERSION_NUM >= PG_VERSION_14 + getObjectIdentityParts(&collationAddress, &objName, &objArgs, false); + #else + getObjectIdentityParts(&collationAddress, &objName, &objArgs); + #endif + + collClause->collname = NIL; + char *name = NULL; + foreach_ptr(name, objName) + { + collClause->collname = lappend(collClause->collname, makeString(name)); + } +} diff --git a/src/backend/distributed/deparser/qualify_type_stmt.c b/src/backend/distributed/deparser/qualify_type_stmt.c index 506491f47..33c80f527 100644 --- a/src/backend/distributed/deparser/qualify_type_stmt.c +++ b/src/backend/distributed/deparser/qualify_type_stmt.c @@ -31,13 +31,10 @@ #include "utils/syscache.h" #include "utils/lsyscache.h" -static char * GetTypeNamespaceNameByNameList(List *names); -static Oid TypeOidGetNamespaceOid(Oid typeOid); - /* * GetTypeNamespaceNameByNameList resolved the schema name of a type by its namelist. */ -static char * +char * GetTypeNamespaceNameByNameList(List *names) { TypeName *typeName = makeTypeNameFromNameList(names); @@ -51,7 +48,7 @@ GetTypeNamespaceNameByNameList(List *names) /* * TypeOidGetNamespaceOid resolves the namespace oid for a type identified by its type oid */ -static Oid +Oid TypeOidGetNamespaceOid(Oid typeOid) { HeapTuple typeTuple = SearchSysCache1(TYPEOID, typeOid); diff --git a/src/backend/distributed/executor/multi_executor.c b/src/backend/distributed/executor/multi_executor.c index 67512cdac..435ab1ea5 100644 --- a/src/backend/distributed/executor/multi_executor.c +++ b/src/backend/distributed/executor/multi_executor.c @@ -763,6 +763,11 @@ GetObjectTypeString(ObjectType objType) return "database"; } + case OBJECT_DOMAIN: + { + return "domain"; + } + case OBJECT_EXTENSION: { return "extension"; diff --git a/src/backend/distributed/metadata/dependency.c b/src/backend/distributed/metadata/dependency.c index 2f4294631..76797d58a 100644 --- a/src/backend/distributed/metadata/dependency.c +++ b/src/backend/distributed/metadata/dependency.c @@ -10,6 +10,7 @@ #include "postgres.h" +#include "distributed/commands.h" #include "distributed/pg_version_constants.h" #include "access/genam.h" @@ -21,6 +22,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_class.h" +#include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc_d.h" @@ -127,6 +129,7 @@ static List * GetRelationTriggerFunctionDependencyList(Oid relationId); static List * GetRelationStatsSchemaDependencyList(Oid relationId); static List * GetRelationIndicesDependencyList(Oid relationId); static DependencyDefinition * CreateObjectAddressDependencyDef(Oid classId, Oid objectId); +static List * GetTypeConstraintDependencyDefinition(Oid typeId); static List * CreateObjectAddressDependencyDefList(Oid classId, List *objectIdList); static ObjectAddress DependencyDefinitionObjectAddress(DependencyDefinition *definition); @@ -631,6 +634,15 @@ SupportedDependencyByCitus(const ObjectAddress *address) return IsObjectAddressOwnedByExtension(address, NULL); } + case OCLASS_CONSTRAINT: + { + /* + * Constraints are only supported when on domain types. Other constraints have + * their typid set to InvalidOid. + */ + return OidIsValid(get_constraint_typid(address->objectId)); + } + case OCLASS_COLLATION: { return true; @@ -692,6 +704,7 @@ SupportedDependencyByCitus(const ObjectAddress *address) { case TYPTYPE_ENUM: case TYPTYPE_COMPOSITE: + case TYPTYPE_DOMAIN: { return true; } @@ -1198,17 +1211,36 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe { case TypeRelationId: { - /* - * types depending on other types are not captured in pg_depend, instead they - * are described with their dependencies by the relation that describes the - * composite type. - */ - if (get_typtype(target.objectId) == TYPTYPE_COMPOSITE) + switch (get_typtype(target.objectId)) { - Oid typeRelationId = get_typ_typrelid(target.objectId); - DependencyDefinition *dependency = - CreateObjectAddressDependencyDef(RelationRelationId, typeRelationId); - result = lappend(result, dependency); + /* + * types depending on other types are not captured in pg_depend, instead + * they are described with their dependencies by the relation that + * describes the composite type. + */ + case TYPTYPE_COMPOSITE: + { + Oid typeRelationId = get_typ_typrelid(target.objectId); + DependencyDefinition *dependency = + CreateObjectAddressDependencyDef(RelationRelationId, + typeRelationId); + result = lappend(result, dependency); + break; + } + + /* + * Domains can have constraints associated with them. Constraints themself + * can depend on things like functions. To support the propagation of + * these functions we will add the constraints to the list of objects to + * be created. + */ + case TYPTYPE_DOMAIN: + { + List *dependencies = + GetTypeConstraintDependencyDefinition(target.objectId); + result = list_concat(result, dependencies); + break; + } } /* @@ -1331,7 +1363,7 @@ GetRelationRuleReferenceDependencyList(Oid relationId) List *ruleDependencies = DependencyDefinitionFromPgDepend(ruleAddress); DependencyDefinition *dependencyDef = NULL; - foreach_ptr(dependencyDefinition, ruleDependencies) + foreach_ptr(dependencyDef, ruleDependencies) { /* Do not add internal dependencies and relation itself */ if (dependencyDef->data.pg_depend.deptype == DEPENDENCY_INTERNAL || @@ -1442,6 +1474,49 @@ GetRelationTriggerFunctionDependencyList(Oid relationId) } +/* + * GetTypeConstraintDependencyDefinition creates a list of constraint dependency + * definitions for a given type + */ +static List * +GetTypeConstraintDependencyDefinition(Oid typeId) +{ + /* lookup and look all constraints to add them to the CreateDomainStmt */ + Relation conRel = table_open(ConstraintRelationId, AccessShareLock); + + /* Look for CHECK Constraints on this domain */ + ScanKeyData key[1]; + ScanKeyInit(&key[0], + Anum_pg_constraint_contypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(typeId)); + + SysScanDesc scan = systable_beginscan(conRel, ConstraintTypidIndexId, true, NULL, 1, + key); + + List *dependencies = NIL; + HeapTuple conTup = NULL; + while (HeapTupleIsValid(conTup = systable_getnext(scan))) + { + Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup); + + if (c->contype != CONSTRAINT_CHECK) + { + /* Ignore non-CHECK constraints, shouldn't be any */ + continue; + } + + dependencies = lappend(dependencies, CreateObjectAddressDependencyDef( + ConstraintRelationId, c->oid)); + } + + systable_endscan(scan); + table_close(conRel, NoLock); + + return dependencies; +} + + /* * CreateObjectAddressDependencyDef returns DependencyDefinition object that * stores the ObjectAddress for the database object identified by classId and diff --git a/src/backend/distributed/metadata/node_metadata.c b/src/backend/distributed/metadata/node_metadata.c index e857e1dda..c66b0d3e2 100644 --- a/src/backend/distributed/metadata/node_metadata.c +++ b/src/backend/distributed/metadata/node_metadata.c @@ -524,7 +524,7 @@ citus_disable_node(PG_FUNCTION_ARGS) TransactionModifiedNodeMetadata = true; /* - * We have not propagated the metadata changes yet, make sure that all the + * We have not propagated the node metadata changes yet, make sure that all the * active nodes get the metadata updates. We defer this operation to the * background worker to make it possible disabling nodes when multiple nodes * are down. @@ -537,7 +537,7 @@ citus_disable_node(PG_FUNCTION_ARGS) */ if (UnsetMetadataSyncedForAll()) { - TriggerMetadataSyncOnCommit(); + TriggerNodeMetadataSyncOnCommit(); } PG_RETURN_VOID(); @@ -1321,7 +1321,7 @@ citus_update_node(PG_FUNCTION_ARGS) */ if (UnsetMetadataSyncedForAll()) { - TriggerMetadataSyncOnCommit(); + TriggerNodeMetadataSyncOnCommit(); } if (handle != NULL) diff --git a/src/backend/distributed/planner/multi_explain.c b/src/backend/distributed/planner/multi_explain.c index 43dca96b7..4c6370e5a 100644 --- a/src/backend/distributed/planner/multi_explain.c +++ b/src/backend/distributed/planner/multi_explain.c @@ -316,6 +316,8 @@ ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es) es->indent += 3; } + ExplainOpenGroup("Subplan", NULL, true, es); + if (es->analyze) { if (es->timing) @@ -358,9 +360,14 @@ ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es) } #endif + ExplainOpenGroup("PlannedStmt", "PlannedStmt", false, es); + ExplainOnePlanCompat(plan, into, es, queryString, params, NULL, &planduration, (es->buffers ? &bufusage : NULL)); + ExplainCloseGroup("PlannedStmt", "PlannedStmt", false, es); + ExplainCloseGroup("Subplan", NULL, true, es); + if (es->format == EXPLAIN_FORMAT_TEXT) { es->indent -= 3; diff --git a/src/backend/distributed/relay/relay_event_utility.c b/src/backend/distributed/relay/relay_event_utility.c index 1643f82c6..7388ff383 100644 --- a/src/backend/distributed/relay/relay_event_utility.c +++ b/src/backend/distributed/relay/relay_event_utility.c @@ -158,7 +158,7 @@ RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId) AccessShareLock, missingOk); - if (constraint->contype == CONSTR_PRIMARY && constraint->indexname) + if (constraint->indexname) { char **indexName = &(constraint->indexname); AppendShardIdToName(indexName, shardId); diff --git a/src/backend/distributed/test/metadata_sync.c b/src/backend/distributed/test/metadata_sync.c index 667c43c2a..b1c8a095c 100644 --- a/src/backend/distributed/test/metadata_sync.c +++ b/src/backend/distributed/test/metadata_sync.c @@ -159,7 +159,7 @@ wait_until_metadata_sync(PG_FUNCTION_ARGS) Datum trigger_metadata_sync(PG_FUNCTION_ARGS) { - TriggerMetadataSyncOnCommit(); + TriggerNodeMetadataSyncOnCommit(); PG_RETURN_VOID(); } diff --git a/src/backend/distributed/transaction/transaction_management.c b/src/backend/distributed/transaction/transaction_management.c index ccd3a4e58..0337411cb 100644 --- a/src/backend/distributed/transaction/transaction_management.c +++ b/src/backend/distributed/transaction/transaction_management.c @@ -115,8 +115,8 @@ AllowedDistributionColumn AllowedDistributionColumnValue; /* if disabled, distributed statements in a function may run as separate transactions */ bool FunctionOpensTransactionBlock = true; -/* if true, we should trigger metadata sync on commit */ -bool MetadataSyncOnCommit = false; +/* if true, we should trigger node metadata sync on commit */ +bool NodeMetadataSyncOnCommit = false; /* transaction management functions */ @@ -288,11 +288,11 @@ CoordinatedTransactionCallback(XactEvent event, void *arg) /* * Changes to catalog tables are now visible to the metadata sync - * daemon, so we can trigger metadata sync if necessary. + * daemon, so we can trigger node metadata sync if necessary. */ - if (MetadataSyncOnCommit) + if (NodeMetadataSyncOnCommit) { - TriggerMetadataSync(MyDatabaseId); + TriggerNodeMetadataSync(MyDatabaseId); } ResetGlobalVariables(); @@ -554,7 +554,7 @@ ResetGlobalVariables() activeSetStmts = NULL; ShouldCoordinatedTransactionUse2PC = false; TransactionModifiedNodeMetadata = false; - MetadataSyncOnCommit = false; + NodeMetadataSyncOnCommit = false; InTopLevelDelegatedFunctionCall = false; ResetWorkerErrorIndication(); memset(&AllowedDistributionColumnValue, 0, @@ -800,14 +800,14 @@ MaybeExecutingUDF(void) /* - * TriggerMetadataSyncOnCommit sets a flag to do metadata sync on commit. - * This is because new metadata only becomes visible to the metadata sync - * daemon after commit happens. + * TriggerNodeMetadataSyncOnCommit sets a flag to do node metadata sync + * on commit. This is because new metadata only becomes visible to the + * metadata sync daemon after commit happens. */ void -TriggerMetadataSyncOnCommit(void) +TriggerNodeMetadataSyncOnCommit(void) { - MetadataSyncOnCommit = true; + NodeMetadataSyncOnCommit = true; } diff --git a/src/backend/distributed/utils/maintenanced.c b/src/backend/distributed/utils/maintenanced.c index 940764235..36d4b0b6e 100644 --- a/src/backend/distributed/utils/maintenanced.c +++ b/src/backend/distributed/utils/maintenanced.c @@ -86,7 +86,7 @@ typedef struct MaintenanceDaemonDBData Oid userOid; pid_t workerPid; bool daemonStarted; - bool triggerMetadataSync; + bool triggerNodeMetadataSync; Latch *latch; /* pointer to the background worker's latch */ } MaintenanceDaemonDBData; @@ -233,7 +233,7 @@ InitializeMaintenanceDaemonBackend(void) dbData->daemonStarted = true; dbData->userOid = extensionOwner; dbData->workerPid = 0; - dbData->triggerMetadataSync = false; + dbData->triggerNodeMetadataSync = false; LWLockRelease(&MaintenanceDaemonControl->lock); pid_t pid; @@ -926,11 +926,11 @@ StopMaintenanceDaemon(Oid databaseId) /* - * TriggerMetadataSync triggers the maintenance daemon to do a metadata sync for - * the given database. + * TriggerMetadataSync triggers the maintenance daemon to do + * a node metadata sync for the given database. */ void -TriggerMetadataSync(Oid databaseId) +TriggerNodeMetadataSync(Oid databaseId) { bool found = false; @@ -942,7 +942,7 @@ TriggerMetadataSync(Oid databaseId) HASH_FIND, &found); if (found) { - dbData->triggerMetadataSync = true; + dbData->triggerNodeMetadataSync = true; /* set latch to wake-up the maintenance loop */ SetLatch(dbData->latch); @@ -961,8 +961,8 @@ MetadataSyncTriggeredCheckAndReset(MaintenanceDaemonDBData *dbData) { LWLockAcquire(&MaintenanceDaemonControl->lock, LW_EXCLUSIVE); - bool metadataSyncTriggered = dbData->triggerMetadataSync; - dbData->triggerMetadataSync = false; + bool metadataSyncTriggered = dbData->triggerNodeMetadataSync; + dbData->triggerNodeMetadataSync = false; LWLockRelease(&MaintenanceDaemonControl->lock); diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index 5cd38b351..cd3927ec1 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -171,6 +171,35 @@ extern List * PostprocessAlterDatabaseOwnerStmt(Node *node, const char *queryStr extern ObjectAddress AlterDatabaseOwnerObjectAddress(Node *node, bool missing_ok); extern List * DatabaseOwnerDDLCommands(const ObjectAddress *address); +/* domain.c - forward declarations */ +extern List * PreprocessCreateDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PostprocessCreateDomainStmt(Node *node, const char *queryString); +extern List * PreprocessDropDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PreprocessAlterDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PostprocessAlterDomainStmt(Node *node, const char *queryString); +extern List * PreprocessDomainRenameConstraintStmt(Node *node, const char *queryString, + ProcessUtilityContext + processUtilityContext); +extern List * PreprocessAlterDomainOwnerStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PostprocessAlterDomainOwnerStmt(Node *node, const char *queryString); +extern List * PreprocessRenameDomainStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PreprocessAlterDomainSchemaStmt(Node *node, const char *queryString, + ProcessUtilityContext processUtilityContext); +extern List * PostprocessAlterDomainSchemaStmt(Node *node, const char *queryString); +extern ObjectAddress CreateDomainStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress AlterDomainStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress DomainRenameConstraintStmtObjectAddress(Node *node, + bool missing_ok); +extern ObjectAddress AlterDomainOwnerStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress RenameDomainStmtObjectAddress(Node *node, bool missing_ok); +extern CreateDomainStmt * RecreateDomainStmt(Oid domainOid); +extern Oid get_constraint_typid(Oid conoid); + /* extension.c - forward declarations */ extern bool IsDropCitusExtensionStmt(Node *parsetree); extern bool IsCreateAlterExtensionUpdateCitusStmt(Node *parsetree); diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index 401dd5652..d35ddaf91 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -50,6 +50,23 @@ extern void QualifyRenameCollationStmt(Node *stmt); extern void QualifyAlterCollationSchemaStmt(Node *stmt); extern void QualifyAlterCollationOwnerStmt(Node *stmt); +/* forward declarations for deparse_domain_stmts.c */ +extern char * DeparseCreateDomainStmt(Node *node); +extern char * DeparseDropDomainStmt(Node *node); +extern char * DeparseAlterDomainStmt(Node *node); +extern char * DeparseDomainRenameConstraintStmt(Node *node); +extern char * DeparseAlterDomainOwnerStmt(Node *node); +extern char * DeparseRenameDomainStmt(Node *node); +extern char * DeparseAlterDomainSchemaStmt(Node *node); + +extern void QualifyCreateDomainStmt(Node *node); +extern void QualifyDropDomainStmt(Node *node); +extern void QualifyAlterDomainStmt(Node *node); +extern void QualifyDomainRenameConstraintStmt(Node *node); +extern void QualifyAlterDomainOwnerStmt(Node *node); +extern void QualifyRenameDomainStmt(Node *node); +extern void QualifyAlterDomainSchemaStmt(Node *node); + /* forward declarations for deparse_foreign_server_stmts.c */ extern char * DeparseCreateForeignServerStmt(Node *node); extern char * DeparseAlterForeignServerStmt(Node *node); @@ -121,12 +138,16 @@ extern void QualifyCreateEnumStmt(Node *stmt); extern void QualifyAlterTypeSchemaStmt(Node *stmt); extern void QualifyAlterTypeOwnerStmt(Node *stmt); -extern void QualifyDropViewStmt(Node *node); -extern void AddViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid); +extern char * GetTypeNamespaceNameByNameList(List *names); +extern Oid TypeOidGetNamespaceOid(Oid typeOid); extern ObjectAddress GetObjectAddressFromParseTree(Node *parseTree, bool missing_ok); extern ObjectAddress RenameAttributeStmtObjectAddress(Node *stmt, bool missing_ok); +/* forward declarations for deparse_view_stmts.c */ +extern void QualifyDropViewStmt(Node *node); +extern void AddViewDefinitionToCreateViewCommand(StringInfo buf, Oid viewOid); + /* forward declarations for deparse_function_stmts.c */ extern char * DeparseDropFunctionStmt(Node *stmt); extern char * DeparseAlterFunctionStmt(Node *stmt); diff --git a/src/include/distributed/maintenanced.h b/src/include/distributed/maintenanced.h index 7ff42674f..c5002021d 100644 --- a/src/include/distributed/maintenanced.h +++ b/src/include/distributed/maintenanced.h @@ -22,7 +22,7 @@ extern double DistributedDeadlockDetectionTimeoutFactor; extern void StopMaintenanceDaemon(Oid databaseId); -extern void TriggerMetadataSync(Oid databaseId); +extern void TriggerNodeMetadataSync(Oid databaseId); extern void InitializeMaintenanceDaemon(void); extern void InitializeMaintenanceDaemonBackend(void); extern bool LockCitusExtension(void); diff --git a/src/include/distributed/transaction_management.h b/src/include/distributed/transaction_management.h index 5ff34e21a..2c958d041 100644 --- a/src/include/distributed/transaction_management.h +++ b/src/include/distributed/transaction_management.h @@ -131,7 +131,7 @@ extern void InitializeTransactionManagement(void); /* other functions */ extern List * ActiveSubXactContexts(void); extern StringInfo BeginAndSetDistributedTransactionIdCommand(void); -extern void TriggerMetadataSyncOnCommit(void); +extern void TriggerNodeMetadataSyncOnCommit(void); #endif /* TRANSACTION_MANAGMENT_H */ diff --git a/src/test/regress/expected/binary_protocol.out b/src/test/regress/expected/binary_protocol.out index 607f3d89d..85fbbdecf 100644 --- a/src/test/regress/expected/binary_protocol.out +++ b/src/test/regress/expected/binary_protocol.out @@ -133,18 +133,8 @@ CREATE TYPE nested_composite_type AS ( a composite_type, b composite_type ); -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.composite_type_domain AS binary_protocol.composite_type$$); - run_command_on_master_and_workers ---------------------------------------------------------------------- - -(1 row) - -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.nested_composite_type_domain AS binary_protocol.nested_composite_type$$); - run_command_on_master_and_workers ---------------------------------------------------------------------- - -(1 row) - +CREATE DOMAIN binary_protocol.composite_type_domain AS binary_protocol.composite_type; +CREATE DOMAIN binary_protocol.nested_composite_type_domain AS binary_protocol.nested_composite_type; INSERT INTO composite_type_table(col) VALUES ((1, 2)::composite_type); SELECT col FROM composite_type_table; col @@ -220,18 +210,8 @@ CREATE TYPE binaryless_composite_type AS ( a aclitem, b aclitem ); -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.binaryless_domain AS aclitem$$); - run_command_on_master_and_workers ---------------------------------------------------------------------- - -(1 row) - -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.binaryless_composite_domain AS binary_protocol.binaryless_composite_type$$); - run_command_on_master_and_workers ---------------------------------------------------------------------- - -(1 row) - +CREATE DOMAIN binary_protocol.binaryless_domain AS aclitem; +CREATE DOMAIN binary_protocol.binaryless_composite_domain AS binary_protocol.binaryless_composite_type; INSERT INTO binaryless_builtin VALUES ('user postgres=r/postgres', 'test'); SELECT col1 FROM binaryless_builtin; col1 diff --git a/src/test/regress/expected/citus_local_table_triggers.out b/src/test/regress/expected/citus_local_table_triggers.out index fa89b78d4..a5925cb25 100644 --- a/src/test/regress/expected/citus_local_table_triggers.out +++ b/src/test/regress/expected/citus_local_table_triggers.out @@ -33,18 +33,18 @@ NOTICE: executing the command locally: SELECT worker_apply_inter_shard_ddl_comm --------------------------------------------------------------------- BEGIN; CREATE TABLE distributed_table(value int); - SELECT create_distributed_table('distributed_table', 'value'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - CREATE FUNCTION insert_42() RETURNS trigger AS $insert_42$ BEGIN INSERT INTO distributed_table VALUES (42); RETURN NEW; END; $insert_42$ LANGUAGE plpgsql; + SELECT create_distributed_table('distributed_table', 'value'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + CREATE TRIGGER insert_42_trigger AFTER DELETE ON citus_local_table FOR EACH ROW EXECUTE FUNCTION insert_42(); diff --git a/src/test/regress/expected/coordinator_evaluation.out b/src/test/regress/expected/coordinator_evaluation.out index a0ed7ea51..f7c648d97 100644 --- a/src/test/regress/expected/coordinator_evaluation.out +++ b/src/test/regress/expected/coordinator_evaluation.out @@ -589,30 +589,13 @@ SELECT count(*) FROM coordinator_evaluation_table_2 WHERE key = 101; CREATE TYPE comptype_int as (int_a int); CREATE DOMAIN domain_comptype_int AS comptype_int CHECK ((VALUE).int_a > 0); --- citus does not propagate domain types --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. -SELECT run_command_on_workers( -$$ - CREATE DOMAIN coordinator_evaluation.domain_comptype_int AS coordinator_evaluation.comptype_int CHECK ((VALUE).int_a > 0) -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,"CREATE DOMAIN") - (localhost,57638,t,"CREATE DOMAIN") -(2 rows) - CREATE TABLE reference_table(column_a coordinator_evaluation.domain_comptype_int); --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; SELECT create_reference_table('reference_table'); create_reference_table --------------------------------------------------------------------- (1 row) -RESET citus.enable_metadata_sync; INSERT INTO reference_table (column_a) VALUES ('(1)'); INSERT INTO reference_table (column_a) VALUES ('(2)'), ('(3)'); INSERT INTO reference_table VALUES ('(4)'), ('(5)'); diff --git a/src/test/regress/expected/distributed_domain.out b/src/test/regress/expected/distributed_domain.out new file mode 100644 index 000000000..7749bbd9a --- /dev/null +++ b/src/test/regress/expected/distributed_domain.out @@ -0,0 +1,947 @@ +CREATE SCHEMA distributed_domain; +SET search_path TO distributed_domain; +SET citus.next_shard_id TO 93631000; +-- verify domain is not already present on workers +SELECT * FROM run_command_on_workers($$ + SELECT 'distributed_domain.age'::regtype; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: type "distributed_domain.age" does not exist + localhost | 57638 | f | ERROR: type "distributed_domain.age" does not exist +(2 rows) + +CREATE DOMAIN age AS int CHECK( VALUE >= 0 ); +-- check domain exists on workers to proof the domain got propagated +SELECT * FROM run_command_on_workers($$ + SELECT 'distributed_domain.age'::regtype; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | distributed_domain.age + localhost | 57638 | t | distributed_domain.age +(2 rows) + +-- verify the constraint triggers when operations that conflict are pushed to the shards +CREATE TABLE foo (a int); +SELECT create_distributed_table('foo', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +CREATE TABLE bar (a age); +SELECT create_distributed_table('bar', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO foo (a) VALUES (-1); -- foo can have negative values +INSERT INTO bar (a) SELECT a FROM foo; -- bar cannot +ERROR: value for domain distributed_domain.age violates check constraint "age_check" +CONTEXT: while executing command on localhost:xxxxx +DROP TABLE bar; -- need to drop this one directly, there is currently a bug in drop schema cascade when it drops both a table and the type of the distribution column at the same time +-- create a domain that is not propagated +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN us_postal_code AS TEXT + CHECK( + VALUE ~ '^\d{5}$' + OR VALUE ~ '^\d{5}-\d{4}$' + ); +RESET citus.enable_ddl_propagation; +-- and use in distributed table to trigger domain propagation +CREATE TABLE us_snail_addy ( + address_id SERIAL PRIMARY KEY, + street1 TEXT NOT NULL, + street2 TEXT, + street3 TEXT, + city TEXT NOT NULL, + postal us_postal_code NOT NULL +); +SELECT create_distributed_table('us_snail_addy', 'address_id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- defaults are marked as a constraint on the statement, which makes it interesting for deparsing, so some extensive +-- test coverage to make sure it works in all strange cases. +CREATE SCHEMA distributed_domain_constraints; +SET search_path TO distributed_domain_constraints; +CREATE DOMAIN with_default AS int DEFAULT 0; +CREATE DOMAIN age_with_default AS int DEFAULT 0 CHECK (value > 0); +CREATE DOMAIN age_with_default2 AS int CHECK (value > 0) DEFAULT 0; +CREATE DOMAIN age_with_default3 AS int CHECK (value > 0) DEFAULT NULL; +CREATE DOMAIN age_with_default4 AS int NOT NULL CHECK (value > 0) DEFAULT NULL; +-- test casting with worker queries +-- should simply work, has no check constraints +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.with_default; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.with_default; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 0 + localhost | 57638 | t | 0 +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.with_default; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +-- has a constraint where the number needs to be greater than 0 +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain_constraints.age_with_default violates check constraint "age_with_default_check" + localhost | 57638 | f | ERROR: value for domain distributed_domain_constraints.age_with_default violates check constraint "age_with_default_check" +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +-- has a constraint where the number needs to be greater than 0 +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default2; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default2; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain_constraints.age_with_default2 violates check constraint "age_with_default2_check" + localhost | 57638 | f | ERROR: value for domain distributed_domain_constraints.age_with_default2 violates check constraint "age_with_default2_check" +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default2; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +-- has a constraint where the number needs to be greater than 0 +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default3; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default3; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain_constraints.age_with_default3 violates check constraint "age_with_default3_check" + localhost | 57638 | f | ERROR: value for domain distributed_domain_constraints.age_with_default3 violates check constraint "age_with_default3_check" +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default3; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +-- has a constraint where the number needs to be greater than 0 and not null +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default4; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: domain distributed_domain_constraints.age_with_default4 does not allow null values + localhost | 57638 | f | ERROR: domain distributed_domain_constraints.age_with_default4 does not allow null values +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default4; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain_constraints.age_with_default4 violates check constraint "age_with_default4_check" + localhost | 57638 | f | ERROR: value for domain distributed_domain_constraints.age_with_default4 violates check constraint "age_with_default4_check" +(2 rows) + +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default4; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +-- test usage in distributed tables +-- we use all domains defined earlier and insert all possible values, NULL, default, non-violation, violation. Some of +-- the default values will violate a constraint. +-- Based on the constraints on the domain we see different errors and will end up with a final set of data stored. +CREATE TABLE use_default (a int, b with_default); +SELECT create_distributed_table('use_default', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_default (a, b) VALUES (0, NULL); +INSERT INTO use_default (a) VALUES (1); +INSERT INTO use_default (a, b) VALUES (2, 1); +INSERT INTO use_default (a, b) VALUES (3, -1); +SELECT * FROM use_default ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 1 | 0 + 2 | 1 + 3 | -1 +(4 rows) + +CREATE TABLE use_age_default (a int, b age_with_default); +SELECT create_distributed_table('use_age_default', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default (a, b) VALUES (0, NULL); +INSERT INTO use_age_default (a) VALUES (1); +ERROR: value for domain distributed_domain_constraints.age_with_default violates check constraint "age_with_default_check" +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default (a, b) VALUES (2, 1); +INSERT INTO use_age_default (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default violates check constraint "age_with_default_check" +CONTEXT: while executing command on localhost:xxxxx +-- also load some data with copy to verify coercions. +\COPY use_age_default FROM STDIN DELIMITER AS ','; +ERROR: value for domain age_with_default violates check constraint "age_with_default_check" +CONTEXT: COPY use_age_default, line 1, column b: " -1" +\COPY use_age_default FROM STDIN DELIMITER AS ','; +SELECT * FROM use_age_default ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 2 | 1 + 5 | 1 +(3 rows) + +CREATE TABLE use_age_default2 (a int, b age_with_default2); +SELECT create_distributed_table('use_age_default2', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default2 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default2 (a) VALUES (1); +ERROR: value for domain distributed_domain_constraints.age_with_default2 violates check constraint "age_with_default2_check" +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default2 (a, b) VALUES (2, 1); +INSERT INTO use_age_default2 (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default2 violates check constraint "age_with_default2_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default2 ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 2 | 1 +(2 rows) + +CREATE TABLE use_age_default3 (a int, b age_with_default3); +SELECT create_distributed_table('use_age_default3', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default3 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default3 (a) VALUES (1); +INSERT INTO use_age_default3 (a, b) VALUES (2, 1); +INSERT INTO use_age_default3 (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default3 violates check constraint "age_with_default3_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default3 ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 1 | + 2 | 1 +(3 rows) + +CREATE TABLE use_age_default4 (a int, b age_with_default4); +SELECT create_distributed_table('use_age_default4', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default4 (a, b) VALUES (0, NULL); +ERROR: domain distributed_domain_constraints.age_with_default4 does not allow null values +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default4 (a) VALUES (1); +ERROR: domain distributed_domain_constraints.age_with_default4 does not allow null values +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default4 (a, b) VALUES (2, 1); +INSERT INTO use_age_default4 (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default4 violates check constraint "age_with_default4_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default4 ORDER BY a; + a | b +--------------------------------------------------------------------- + 2 | 1 +(1 row) + +SET client_min_messages TO warning; +DROP SCHEMA distributed_domain_constraints CASCADE; +RESET client_min_messages; +-- same tests as above, with with just in time propagation of domains +CREATE SCHEMA distributed_domain_constraints; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN with_default AS int DEFAULT 0; +CREATE DOMAIN age_with_default AS int DEFAULT 0 CHECK (value > 0); +CREATE DOMAIN age_with_default2 AS int CHECK (value > 0) DEFAULT 0; +CREATE DOMAIN age_with_default3 AS int CHECK (value > 0) DEFAULT NULL; +CREATE DOMAIN age_with_default4 AS int NOT NULL CHECK (value > 0) DEFAULT NULL; +RESET citus.enable_ddl_propagation; +-- use all domains in tables to get them propagated +CREATE TABLE use_default (a int, b with_default); +SELECT create_distributed_table('use_default', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_default (a, b) VALUES (0, NULL); +INSERT INTO use_default (a) VALUES (1); +INSERT INTO use_default (a, b) VALUES (2, 1); +INSERT INTO use_default (a, b) VALUES (3, -1); +SELECT * FROM use_default ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 1 | 0 + 2 | 1 + 3 | -1 +(4 rows) + +CREATE TABLE use_age_default (a int, b age_with_default); +SELECT create_distributed_table('use_age_default', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default (a, b) VALUES (0, NULL); +INSERT INTO use_age_default (a) VALUES (1); +ERROR: value for domain distributed_domain_constraints.age_with_default violates check constraint "age_with_default_check" +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default (a, b) VALUES (2, 1); +INSERT INTO use_age_default (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default violates check constraint "age_with_default_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 2 | 1 +(2 rows) + +CREATE TABLE use_age_default2 (a int, b age_with_default2); +SELECT create_distributed_table('use_age_default2', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default2 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default2 (a) VALUES (1); +ERROR: value for domain distributed_domain_constraints.age_with_default2 violates check constraint "age_with_default2_check" +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default2 (a, b) VALUES (2, 1); +INSERT INTO use_age_default2 (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default2 violates check constraint "age_with_default2_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default2 ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 2 | 1 +(2 rows) + +CREATE TABLE use_age_default3 (a int, b age_with_default3); +SELECT create_distributed_table('use_age_default3', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default3 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default3 (a) VALUES (1); +INSERT INTO use_age_default3 (a, b) VALUES (2, 1); +INSERT INTO use_age_default3 (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default3 violates check constraint "age_with_default3_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default3 ORDER BY a; + a | b +--------------------------------------------------------------------- + 0 | + 1 | + 2 | 1 +(3 rows) + +CREATE TABLE use_age_default4 (a int, b age_with_default4); +SELECT create_distributed_table('use_age_default4', 'a', shard_count => 2); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_default4 (a, b) VALUES (0, NULL); +ERROR: domain distributed_domain_constraints.age_with_default4 does not allow null values +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default4 (a) VALUES (1); +ERROR: domain distributed_domain_constraints.age_with_default4 does not allow null values +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_age_default4 (a, b) VALUES (2, 1); +INSERT INTO use_age_default4 (a, b) VALUES (3, -1); +ERROR: value for domain distributed_domain_constraints.age_with_default4 violates check constraint "age_with_default4_check" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_default4 ORDER BY a; + a | b +--------------------------------------------------------------------- + 2 | 1 +(1 row) + +-- clean up +SET client_min_messages TO warning; +DROP SCHEMA distributed_domain_constraints CASCADE; +RESET client_min_messages; +CREATE SCHEMA postgres_domain_examples; +SET search_path TO postgres_domain_examples; +-- make sure the function gets automatically propagated when we propagate the domain +SET citus.enable_ddl_propagation TO off; +create function sql_is_distinct_from(anyelement, anyelement) + returns boolean language sql + as 'select $1 is distinct from $2 limit 1'; +RESET citus.enable_ddl_propagation; +CREATE DOMAIN inotnull int + CHECK (sql_is_distinct_from(value, null)); +SELECT * FROM run_command_on_workers($$ SELECT 1::postgres_domain_examples.inotnull; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT null::postgres_domain_examples.inotnull; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain postgres_domain_examples.inotnull violates check constraint "inotnull_check" + localhost | 57638 | f | ERROR: value for domain postgres_domain_examples.inotnull violates check constraint "inotnull_check" +(2 rows) + +-- create a domain with sql function as a default value +SET citus.enable_ddl_propagation TO off; +create function random_between(min int, max int) + returns int language sql + as 'SELECT round(random()*($2-$1))+$1;'; +RESET citus.enable_ddl_propagation; +-- this verifies the function in the default expression is found and distributed, otherwise the creation of the domain would fail on the workers. +CREATE DOMAIN with_random_default int DEFAULT random_between(100, 200); +SET client_min_messages TO warning; +DROP SCHEMA postgres_domain_examples CASCADE; +RESET client_min_messages; +SET search_path TO distributed_domain; +-- verify drops are propagated +CREATE DOMAIN will_drop AS text DEFAULT 'foo'; +DROP DOMAIN will_drop; +-- verify domain is dropped from workers +SELECT * FROM run_command_on_workers($$ SELECT 'dropped?'::distributed_domain.will_drop; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: type "distributed_domain.will_drop" does not exist + localhost | 57638 | f | ERROR: type "distributed_domain.will_drop" does not exist +(2 rows) + +-- verify the type modifiers are deparsed correctly, both for direct propagation as well as on demand propagation +CREATE DOMAIN varcharmod AS varchar(3); +SELECT * FROM run_command_on_workers($$ SELECT '12345'::distributed_domain.varcharmod; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 123 + localhost | 57638 | t | 123 +(2 rows) + +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN varcharmod_ondemand AS varchar(3); +RESET citus.enable_ddl_propagation; +CREATE TABLE use_varcharmod_ondemand (a int, b varcharmod_ondemand); +SELECT create_distributed_table('use_varcharmod_ondemand', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +-- epxected error due to value being too long for varchar(3) +INSERT INTO use_varcharmod_ondemand VALUES (1,'12345'); +ERROR: value too long for type character varying(3) +SELECT * FROM run_command_on_workers($$ SELECT '12345'::distributed_domain.varcharmod_ondemand; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 123 + localhost | 57638 | t | 123 +(2 rows) + +-- section testing default altering +CREATE DOMAIN alter_default AS text DEFAULT 'foo'; +CREATE TABLE use_alter_default (a int, b alter_default); +SELECT create_distributed_table('use_alter_default', 'a', shard_count => 4); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_alter_default (a) VALUES (1); +ALTER DOMAIN alter_default SET DEFAULT 'bar'; +INSERT INTO use_alter_default (a) VALUES (2); +ALTER DOMAIN alter_default DROP DEFAULT; +INSERT INTO use_alter_default (a) VALUES (3); +SELECT * FROM use_alter_default ORDER BY 1,2; + a | b +--------------------------------------------------------------------- + 1 | foo + 2 | bar + 3 | +(3 rows) + +-- add new dependency while adding default +CREATE DOMAIN add_default_with_function AS int; +SET citus.enable_ddl_propagation TO off; +create function random_between(min int, max int) + returns int language sql + as 'SELECT round(random()*($2-$1))+$1;'; +RESET citus.enable_ddl_propagation; +ALTER DOMAIN add_default_with_function SET DEFAULT random_between(100, 200); +CREATE TABLE use_add_default_with_function (a int, b add_default_with_function); +SELECT create_distributed_table('use_add_default_with_function', 'a', shard_count => 4); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_add_default_with_function (a) VALUES (1); +-- altering NULL/NOT NULL +CREATE DOMAIN alter_null AS int; +CREATE TABLE use_alter_null (a int, b alter_null); +SELECT create_distributed_table('use_alter_null', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_alter_null (a) VALUES (1); +ALTER DOMAIN alter_null SET NOT NULL; +ERROR: column "b" of table "use_alter_null_93631040" contains null values +CONTEXT: while executing command on localhost:xxxxx +TRUNCATE use_alter_null; +ALTER DOMAIN alter_null SET NOT NULL; +INSERT INTO use_alter_null (a) VALUES (2); +ERROR: domain distributed_domain.alter_null does not allow null values +CONTEXT: while executing command on localhost:xxxxx +ALTER DOMAIN alter_null DROP NOT NULL; +INSERT INTO use_alter_null (a) VALUES (3); +SELECT * FROM use_alter_null ORDER BY 1; + a | b +--------------------------------------------------------------------- + 3 | +(1 row) + +-- testing adding/dropping constraints +SET citus.enable_ddl_propagation TO off; +create function sql_is_distinct_from(anyelement, anyelement) + returns boolean language sql + as 'select $1 is distinct from $2 limit 1'; +RESET citus.enable_ddl_propagation; +CREATE DOMAIN alter_add_constraint int; +ALTER DOMAIN alter_add_constraint ADD CONSTRAINT check_distinct CHECK (sql_is_distinct_from(value, null)); +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.alter_add_constraint; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT null::distributed_domain.alter_add_constraint; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain.alter_add_constraint violates check constraint "check_distinct" + localhost | 57638 | f | ERROR: value for domain distributed_domain.alter_add_constraint violates check constraint "check_distinct" +(2 rows) + +ALTER DOMAIN alter_add_constraint DROP CONSTRAINT check_distinct; +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.alter_add_constraint; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT null::distributed_domain.alter_add_constraint; $$); + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +ALTER DOMAIN alter_add_constraint DROP CONSTRAINT IF EXISTS check_distinct; +NOTICE: constraint "check_distinct" of domain "distributed_domain.alter_add_constraint" does not exist, skipping +ALTER DOMAIN alter_add_constraint ADD CONSTRAINT check_distinct CHECK (sql_is_distinct_from(value, null)); +ALTER DOMAIN alter_add_constraint RENAME CONSTRAINT check_distinct TO check_distinct_renamed; +ALTER DOMAIN alter_add_constraint DROP CONSTRAINT check_distinct_renamed; +-- test validating invalid constraints +CREATE DOMAIN age_invalid AS int NOT NULL DEFAULT 0; +CREATE TABLE use_age_invalid (a int, b age_invalid); +SELECT create_distributed_table('use_age_invalid', 'a', shard_count => 4); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_age_invalid VALUES (1,1), (2, 2), (3, 0), (4, -1); +ALTER DOMAIN age_invalid ADD CONSTRAINT check_age_positive CHECK (value>=0) NOT VALID; +-- should fail, even though constraint is not valid +INSERT INTO use_age_invalid VALUES (5,-1); +ERROR: value for domain distributed_domain.age_invalid violates check constraint "check_age_positive" +CONTEXT: while executing command on localhost:xxxxx +-- reading violating data of an non-valid constraint errors in citus +SELECT * FROM use_age_invalid ORDER BY 1; +ERROR: value for domain age_invalid violates check constraint "check_age_positive" +-- should fail since there is data in the table that violates the check +ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive; +ERROR: column "b" of table "use_age_invalid_93631045" contains values that violate the new constraint +CONTEXT: while executing command on localhost:xxxxx +DELETE FROM use_age_invalid WHERE b < 0; +-- should succeed now since the violating data has been removed +ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive; +-- still fails for constraint +INSERT INTO use_age_invalid VALUES (5,-1); +ERROR: value for domain distributed_domain.age_invalid violates check constraint "check_age_positive" +CONTEXT: while executing command on localhost:xxxxx +SELECT * FROM use_age_invalid ORDER BY 1; + a | b +--------------------------------------------------------------------- + 1 | 1 + 2 | 2 + 3 | 0 +(3 rows) + +-- verify we can validate a constraint that is already validated, can happen when we add a node while a domain constraint was not validated +ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive; +-- test changing the owner of a domain +SET client_min_messages TO error; +SELECT 1 FROM run_command_on_workers($$ CREATE ROLE domain_owner; $$); + ?column? +--------------------------------------------------------------------- + 1 + 1 +(2 rows) + +CREATE ROLE domain_owner; +RESET client_min_messages; +CREATE DOMAIN alter_domain_owner AS int; +ALTER DOMAIN alter_domain_owner OWNER TO domain_owner; +SELECT u.rolname +FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) +WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; + rolname +--------------------------------------------------------------------- + domain_owner +(1 row) + +SELECT * FROM run_command_on_workers($$ + SELECT u.rolname + FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) + WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | domain_owner + localhost | 57638 | t | domain_owner +(2 rows) + +DROP DOMAIN alter_domain_owner; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN alter_domain_owner AS int; +ALTER DOMAIN alter_domain_owner OWNER TO domain_owner; +RESET citus.enable_ddl_propagation; +CREATE TABLE use_alter_domain_owner (a int, b alter_domain_owner); +SELECT create_distributed_table('use_alter_domain_owner', 'a', shard_count => 4); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT u.rolname +FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) +WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; + rolname +--------------------------------------------------------------------- + domain_owner +(1 row) + +SELECT * FROM run_command_on_workers($$ + SELECT u.rolname + FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) + WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; +$$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | domain_owner + localhost | 57638 | t | domain_owner +(2 rows) + +-- rename the domain +ALTER DOMAIN alter_domain_owner RENAME TO renamed_domain; +SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.renamed_domain; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +-- move schema +SET citus.enable_ddl_propagation TO off; +CREATE SCHEMA distributed_domain_moved; +RESET citus.enable_ddl_propagation; +ALTER DOMAIN renamed_domain SET SCHEMA distributed_domain_moved; +-- test collation +CREATE COLLATION german_phonebook (provider = icu, locale = 'de-u-co-phonebk'); +CREATE DOMAIN with_collation AS text COLLATE german_phonebook NOT NULL; +SELECT run_command_on_workers($$ SELECT typcollation::regcollation FROM pg_type WHERE oid = 'distributed_domain.with_collation'::regtype; $$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,distributed_domain.german_phonebook) + (localhost,57638,t,distributed_domain.german_phonebook) +(2 rows) + +DROP DOMAIN with_collation; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN with_collation AS text COLLATE german_phonebook NOT NULL; +RESET citus.enable_ddl_propagation; +CREATE TABLE use_with_collation (a int, b with_collation); +SELECT create_reference_table('use_with_collation'); + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +SELECT run_command_on_workers($$ SELECT typcollation::regcollation FROM pg_type WHERE oid = 'distributed_domain.with_collation'::regtype; $$); + run_command_on_workers +--------------------------------------------------------------------- + (localhost,57637,t,distributed_domain.german_phonebook) + (localhost,57638,t,distributed_domain.german_phonebook) +(2 rows) + +INSERT INTO use_with_collation VALUES (1, U&'\00E4sop'), (2, 'Vossr'); +SELECT * FROM use_with_collation WHERE b < 'b'; + a | b +--------------------------------------------------------------------- + 1 | äsop +(1 row) + +-- test domain backed by array +CREATE DOMAIN domain_array AS int[] NOT NULL CHECK (array_length(value,1) >= 2); +SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.domain_array; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: domain distributed_domain.domain_array does not allow null values + localhost | 57638 | f | ERROR: domain distributed_domain.domain_array does not allow null values +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT ARRAY[1]::distributed_domain.domain_array; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain.domain_array violates check constraint "domain_array_check" + localhost | 57638 | f | ERROR: value for domain distributed_domain.domain_array violates check constraint "domain_array_check" +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT ARRAY[1,2]::distributed_domain.domain_array; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | {1,2} + localhost | 57638 | t | {1,2} +(2 rows) + +DROP DOMAIN domain_array; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN domain_array AS int[] NOT NULL CHECK (array_length(value,1) >= 2); +RESET citus.enable_ddl_propagation; +CREATE TABLE use_domain_array (a int, b domain_array); +SELECT create_distributed_table('use_domain_array', 'a'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +INSERT INTO use_domain_array VALUES (1, NULL); +ERROR: domain distributed_domain.domain_array does not allow null values +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_domain_array VALUES (2, ARRAY[1]); +ERROR: value for domain distributed_domain.domain_array violates check constraint "domain_array_check" +CONTEXT: while executing command on localhost:xxxxx +INSERT INTO use_domain_array VALUES (3, ARRAY[1,2]); +SELECT * FROM use_domain_array ORDER BY 1; + a | b +--------------------------------------------------------------------- + 3 | {1,2} +(1 row) + +-- add nameless constraint +CREATE DOMAIN nameless_constraint AS int; +ALTER DOMAIN nameless_constraint ADD CHECK (value > 0); +SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.nameless_constraint; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | + localhost | 57638 | t | +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.nameless_constraint; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT (-1)::distributed_domain.nameless_constraint; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: value for domain distributed_domain.nameless_constraint violates check constraint "nameless_constraint_check" + localhost | 57638 | f | ERROR: value for domain distributed_domain.nameless_constraint violates check constraint "nameless_constraint_check" +(2 rows) + +-- Test domains over domains +create domain vchar4 varchar(4); +create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x'); +create domain dtop dinter check (substring(VALUE, 2, 1) = '1'); +create table dtest(f1 dtop, id bigserial); +SELECT create_distributed_table('dtest', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +insert into dtest values('x123'); +insert into dtest values('123'); +ERROR: value for domain dtop violates check constraint "dinter_check" +insert into dtest values('x234'); +ERROR: value for domain dtop violates check constraint "dtop_check" +DROP TABLE dtest; +DROP DOMAIN IF EXISTS dtop; +DROP DOMAIN vchar4; +ERROR: cannot drop type vchar4 because other objects depend on it +DETAIL: type dinter depends on type vchar4 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP DOMAIN vchar4 CASCADE; +NOTICE: drop cascades to type dinter +-- drop multiple domains at once, for which one is not distributed +CREATE DOMAIN domain1 AS int; +CREATE DOMAIN domain2 AS text; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN domain3 AS text; +RESET citus.enable_ddl_propagation; +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.domain1; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain2; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | t | 1 + localhost | 57638 | t | 1 +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain3; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: type "distributed_domain.domain3" does not exist + localhost | 57638 | f | ERROR: type "distributed_domain.domain3" does not exist +(2 rows) + +DROP DOMAIN domain1, domain2, domain3; +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.domain1; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: type "distributed_domain.domain1" does not exist + localhost | 57638 | f | ERROR: type "distributed_domain.domain1" does not exist +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain2; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: type "distributed_domain.domain2" does not exist + localhost | 57638 | f | ERROR: type "distributed_domain.domain2" does not exist +(2 rows) + +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain3; $$) ORDER BY 1,2; + nodename | nodeport | success | result +--------------------------------------------------------------------- + localhost | 57637 | f | ERROR: type "distributed_domain.domain3" does not exist + localhost | 57638 | f | ERROR: type "distributed_domain.domain3" does not exist +(2 rows) + +SET client_min_messages TO warning; +DROP SCHEMA distributed_domain, distributed_domain_moved CASCADE; diff --git a/src/test/regress/expected/distributed_triggers.out b/src/test/regress/expected/distributed_triggers.out index b4ad001f5..4cf0cfee8 100644 --- a/src/test/regress/expected/distributed_triggers.out +++ b/src/test/regress/expected/distributed_triggers.out @@ -314,6 +314,8 @@ ORDER BY shard_key_value, object_id, change_id; DROP TRIGGER bad_shardkey_record_change_trigger ON data; CREATE OR REPLACE FUNCTION remote_shardkey_record_change() RETURNS trigger +SET search_path = 'distributed_triggers' +LANGUAGE plpgsql AS $$ DECLARE last_change_id bigint; @@ -321,12 +323,15 @@ BEGIN UPDATE distributed_triggers.data_changes SET operation_type = TG_OP; RETURN NULL; END; -$$ LANGUAGE plpgsql; +$$; CREATE TRIGGER remote_shardkey_record_change_trigger AFTER INSERT OR UPDATE OR DELETE ON data FOR EACH ROW EXECUTE FUNCTION distributed_triggers.remote_shardkey_record_change(); CREATE FUNCTION insert_document(key text, id text) -RETURNS void LANGUAGE plpgsql AS $fn$ +RETURNS void +LANGUAGE plpgsql +SET search_path = 'distributed_triggers' +AS $fn$ BEGIN INSERT INTO distributed_triggers.data VALUES (key, id, '{"id1":"id2"}'); DELETE FROM distributed_triggers.data where shard_key_value = key; @@ -882,8 +887,8 @@ SELECT run_command_on_workers($$SELECT count(*) FROM pg_trigger WHERE tgname lik (2 rows) RESET client_min_messages; -SET citus.enable_unsafe_triggers TO false; -SELECT run_command_on_workers('ALTER SYSTEM SET citus.enable_unsafe_triggers TO false;'); +RESET citus.enable_unsafe_triggers; +SELECT run_command_on_workers('ALTER SYSTEM RESET citus.enable_unsafe_triggers;'); run_command_on_workers --------------------------------------------------------------------- (localhost,57637,t,"ALTER SYSTEM") diff --git a/src/test/regress/expected/distributed_types.out b/src/test/regress/expected/distributed_types.out index 02d419d9b..524b7016a 100644 --- a/src/test/regress/expected/distributed_types.out +++ b/src/test/regress/expected/distributed_types.out @@ -414,30 +414,13 @@ ERROR: inserting or modifying composite type fields is not supported HINT: Use the column name to insert or update the composite type as a single value CREATE TYPE two_ints as (if1 int, if2 int); CREATE DOMAIN domain AS two_ints CHECK ((VALUE).if1 > 0); --- citus does not propagate domain objects --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. -SELECT run_command_on_workers( -$$ - CREATE DOMAIN type_tests.domain AS type_tests.two_ints CHECK ((VALUE).if1 > 0); -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,"CREATE DOMAIN") - (localhost,57638,t,"CREATE DOMAIN") -(2 rows) - CREATE TABLE domain_indirection_test (f1 int, f3 domain, domain_array domain[]); --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; SELECT create_distributed_table('domain_indirection_test', 'f1'); create_distributed_table --------------------------------------------------------------------- (1 row) -RESET citus.enable_metadata_sync; -- not supported (field indirection to underlying composite type) INSERT INTO domain_indirection_test (f1,f3.if1, f3.if2) VALUES (0, 1, 2); ERROR: inserting or modifying composite type fields is not supported diff --git a/src/test/regress/expected/function_propagation.out b/src/test/regress/expected/function_propagation.out index 297199a98..1ec5736f5 100644 --- a/src/test/regress/expected/function_propagation.out +++ b/src/test/regress/expected/function_propagation.out @@ -321,6 +321,7 @@ $$; -- Show that functions are propagated (or not) as a dependency -- Function as a default column BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_def() RETURNS int LANGUAGE plpgsql AS @@ -329,7 +330,6 @@ BEGIN; return 1; END; $$; - -- Function shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_def'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -360,6 +360,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Multiple functions as a default column BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_1() RETURNS int LANGUAGE plpgsql AS @@ -376,7 +377,6 @@ BEGIN; return 1; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_1'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -434,6 +434,9 @@ BEGIN; return 1; END; $$; +WARNING: "function func_in_transaction_3(non_dist_table)" has dependency to "table non_dist_table" that is not in Citus' metadata +DETAIL: "function func_in_transaction_3(non_dist_table)" will be created only locally +HINT: Distribute "table non_dist_table" first to distribute "function func_in_transaction_3(non_dist_table)" CREATE TABLE table_to_prop_func_3(id int, col_1 int default func_in_transaction_3(NULL::non_dist_table)); -- It should error out as there is a non-distributed table dependency SELECT create_distributed_table('table_to_prop_func_3','id'); @@ -443,12 +446,7 @@ COMMIT; -- Adding a column with default value should propagate the function BEGIN; CREATE TABLE table_to_prop_func_4(id int); - SELECT create_distributed_table('table_to_prop_func_4', 'id'); - create_distributed_table ---------------------------------------------------------------------- - -(1 row) - + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_4() RETURNS int LANGUAGE plpgsql AS @@ -457,7 +455,12 @@ BEGIN; return 1; END; $$; - -- Function shouldn't be propagated within transaction + SELECT create_distributed_table('table_to_prop_func_4', 'id'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_4'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -483,6 +486,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Adding a column with default function depending on non-distributable table should fail BEGIN; CREATE TABLE non_dist_table_for_function(id int); + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION non_dist_func(col_1 non_dist_table_for_function) RETURNS int LANGUAGE plpgsql AS @@ -504,6 +508,7 @@ HINT: Distribute "table non_dist_table_for_function" first to distribute "table ROLLBACK; -- Adding multiple columns with default values should propagate the function BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_5() RETURNS int LANGUAGE plpgsql AS @@ -520,7 +525,6 @@ BEGIN; return 1; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_5'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -569,6 +573,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Adding a constraint with function check should propagate the function BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_7(param_1 int) RETURNS boolean LANGUAGE plpgsql AS @@ -577,7 +582,6 @@ BEGIN; return param_1 > 5; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_7'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -608,6 +612,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Adding a constraint with multiple functions check should propagate the function BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_8(param_1 int) RETURNS boolean LANGUAGE plpgsql AS @@ -624,7 +629,6 @@ BEGIN; return param_1 > 5; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_8'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -680,6 +684,7 @@ BEGIN; (1 row) + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_10(param_1 int) RETURNS boolean LANGUAGE plpgsql AS @@ -688,7 +693,6 @@ BEGIN; return param_1 > 5; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_10'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -722,6 +726,9 @@ BEGIN; return param_1 > 5; END; $$; +WARNING: "function func_in_transaction_11(integer,local_table_for_const)" has dependency to "table local_table_for_const" that is not in Citus' metadata +DETAIL: "function func_in_transaction_11(integer,local_table_for_const)" will be created only locally +HINT: Distribute "table local_table_for_const" first to distribute "function func_in_transaction_11(integer,local_table_for_const)" CREATE TABLE table_to_prop_func_9(id int, col_1 int check (func_in_transaction_11(col_1, NULL::local_table_for_const))); -- It should error out since there is non-distributed table dependency exists SELECT create_distributed_table('table_to_prop_func_9', 'id'); @@ -730,6 +737,7 @@ HINT: Distribute "table local_table_for_const" first to distribute "table table COMMIT; -- Show that function as a part of generated always is supporte BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION non_sense_func_for_generated_always() RETURNS int LANGUAGE plpgsql IMMUTABLE AS @@ -738,7 +746,6 @@ BEGIN; return 1; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.non_sense_func_for_generated_always'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -764,6 +771,7 @@ BEGIN; COMMIT; -- Show that functions depending table via rule are also distributed BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_for_rule() RETURNS int LANGUAGE plpgsql STABLE AS @@ -772,7 +780,7 @@ BEGIN; return 4; END; $$; - -- Functions shouldn't be propagated within transaction + RESET citus.create_object_propagation; SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_for_rule'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -805,6 +813,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Show that functions as partitioning functions are supported BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION non_sense_func_for_partitioning(int) RETURNS int LANGUAGE plpgsql IMMUTABLE AS @@ -813,7 +822,6 @@ BEGIN; return 1; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.non_sense_func_for_partitioning'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -844,6 +852,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Test function dependency on citus local table BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_for_local_table() RETURNS int LANGUAGE plpgsql AS @@ -852,7 +861,6 @@ BEGIN; return 1; END; $$; - -- Function shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_for_local_table'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -882,6 +890,7 @@ NOTICE: localhost:xxxxx is the coordinator and already contains metadata, skipp ROLLBACK; -- Show that having a function dependency on exlude also works BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION exclude_bool_func() RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS @@ -890,7 +899,6 @@ BEGIN; return true; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.exclude_bool_func'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -921,6 +929,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Show that having a function dependency for index also works BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_for_index_predicate(col_1 int) RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS @@ -929,7 +938,6 @@ BEGIN; return col_1 > 5; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_for_index_predicate'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- @@ -978,6 +986,9 @@ BEGIN; return 5; END; $$; +WARNING: "function func_for_func_dep_2(func_dep_table)" has dependency to "table func_dep_table" that is not in Citus' metadata +DETAIL: "function func_for_func_dep_2(func_dep_table)" will be created only locally +HINT: Distribute "table func_dep_table" first to distribute "function func_for_func_dep_2(func_dep_table)" SELECT create_distributed_table('func_dep_table', 'a'); create_distributed_table --------------------------------------------------------------------- @@ -1002,6 +1013,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Test function with SQL language and sequence dependency BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_def_with_seq(val bigint) RETURNS bigint LANGUAGE SQL AS @@ -1014,7 +1026,6 @@ BEGIN; $$ SELECT func_in_transaction_def_with_seq(val); $$; - -- Function shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_def_with_seq'::regproc::oid; pg_identify_object_as_address --------------------------------------------------------------------- diff --git a/src/test/regress/expected/isolation_replicate_reference_tables_to_coordinator.out b/src/test/regress/expected/isolation_replicate_reference_tables_to_coordinator.out index a2792d0c4..cea3efbf1 100644 --- a/src/test/regress/expected/isolation_replicate_reference_tables_to_coordinator.out +++ b/src/test/regress/expected/isolation_replicate_reference_tables_to_coordinator.out @@ -120,7 +120,8 @@ step s2-view-worker: ('%dump_local_%'), ('%citus_internal_local_blocked_processes%'), ('%add_node%'), - ('%csa_from_one_node%')) + ('%csa_from_one_node%'), + ('%pg_locks%')) AND is_worker_query = true AND backend_type = 'client backend' AND query != '' diff --git a/src/test/regress/expected/metadata_sync_helpers.out b/src/test/regress/expected/metadata_sync_helpers.out index e26a84a08..2fd4707b2 100644 --- a/src/test/regress/expected/metadata_sync_helpers.out +++ b/src/test/regress/expected/metadata_sync_helpers.out @@ -694,6 +694,7 @@ BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; SET application_name to 'citus_internal gpid=10000000001'; \set VERBOSITY terse + SET citus.enable_ddl_propagation TO OFF; CREATE FUNCTION distribution_test_function(int) RETURNS int AS $$ SELECT $1 $$ LANGUAGE SQL; diff --git a/src/test/regress/expected/multi_alter_table_add_constraints.out b/src/test/regress/expected/multi_alter_table_add_constraints.out index 56ef04335..e96326c18 100644 --- a/src/test/regress/expected/multi_alter_table_add_constraints.out +++ b/src/test/regress/expected/multi_alter_table_add_constraints.out @@ -617,10 +617,23 @@ ORDER BY localhost | 57638 | t | (2 rows) +CREATE TABLE alter_add_unique(x int, y int); +CREATE UNIQUE INDEX CONCURRENTLY alter_unique_idx ON alter_add_unique(x); +SELECT create_distributed_table('alter_add_unique', 'x'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +ALTER TABLE alter_add_unique ADD CONSTRAINT unique_constraint_test UNIQUE USING INDEX alter_unique_idx; +NOTICE: ALTER TABLE / ADD CONSTRAINT USING INDEX will rename index "alter_unique_idx" to "unique_constraint_test" +ALTER TABLE alter_add_unique DROP CONSTRAINT unique_constraint_test; SET search_path TO 'public'; DROP SCHEMA sc1 CASCADE; NOTICE: drop cascades to table sc1.alter_add_prim_key DROP SCHEMA sc2 CASCADE; NOTICE: drop cascades to table sc2.alter_add_prim_key DROP SCHEMA sc3 CASCADE; -NOTICE: drop cascades to table sc3.alter_add_prim_key +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table sc3.alter_add_prim_key +drop cascades to table sc3.alter_add_unique diff --git a/src/test/regress/expected/multi_explain.out b/src/test/regress/expected/multi_explain.out index 02a6eaab0..bb85ed3d4 100644 --- a/src/test/regress/expected/multi_explain.out +++ b/src/test/regress/expected/multi_explain.out @@ -18,6 +18,16 @@ BEGIN RETURN result; END; $BODY$ LANGUAGE plpgsql; +CREATE FUNCTION explain_analyze_json(query text) +RETURNS jsonb +AS $BODY$ +DECLARE + result jsonb; +BEGIN + EXECUTE format('EXPLAIN (ANALYZE TRUE, FORMAT JSON) %s', query) INTO result; + RETURN result; +END; +$BODY$ LANGUAGE plpgsql; -- Function that parses explain output as XML CREATE FUNCTION explain_xml(query text) RETURNS xml @@ -29,6 +39,17 @@ BEGIN RETURN result; END; $BODY$ LANGUAGE plpgsql; +-- Function that parses explain output as XML +CREATE FUNCTION explain_analyze_xml(query text) +RETURNS xml +AS $BODY$ +DECLARE + result xml; +BEGIN + EXECUTE format('EXPLAIN (ANALYZE true, FORMAT XML) %s', query) INTO result; + RETURN result; +END; +$BODY$ LANGUAGE plpgsql; -- VACUMM related tables to ensure test outputs are stable VACUUM ANALYZE lineitem; VACUUM ANALYZE orders; @@ -135,6 +156,13 @@ SELECT true AS valid FROM explain_json($$ SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity$$); t +SELECT true AS valid FROM explain_analyze_json($$ + WITH a AS ( + SELECT l_quantity, count(*) count_quantity FROM lineitem + GROUP BY l_quantity ORDER BY count_quantity, l_quantity LIMIT 10) + SELECT count(*) FROM a +$$); +t -- Test XML format EXPLAIN (COSTS FALSE, FORMAT XML) SELECT l_quantity, count(*) count_quantity FROM lineitem @@ -208,6 +236,13 @@ SELECT true AS valid FROM explain_xml($$ SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity$$); t +SELECT true AS valid FROM explain_analyze_xml($$ + WITH a AS ( + SELECT l_quantity, count(*) count_quantity FROM lineitem + GROUP BY l_quantity ORDER BY count_quantity, l_quantity LIMIT 10) + SELECT count(*) FROM a +$$); +t -- Test YAML format EXPLAIN (COSTS FALSE, FORMAT YAML) SELECT l_quantity, count(*) count_quantity FROM lineitem diff --git a/src/test/regress/expected/multi_prepare_sql.out b/src/test/regress/expected/multi_prepare_sql.out index 4ff7bba42..e59b9e8f2 100644 --- a/src/test/regress/expected/multi_prepare_sql.out +++ b/src/test/regress/expected/multi_prepare_sql.out @@ -1049,31 +1049,16 @@ SELECT key, value FROM text_partition_column_table ORDER BY key; DROP TABLE text_partition_column_table; -- Domain type columns can give issues CREATE DOMAIN test_key AS text CHECK(VALUE ~ '^test-\d$'); --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. -SELECT run_command_on_workers($$ - CREATE DOMAIN test_key AS text CHECK(VALUE ~ '^test-\d$') -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,"CREATE DOMAIN") - (localhost,57638,t,"CREATE DOMAIN") -(2 rows) - CREATE TABLE domain_partition_column_table ( key test_key NOT NULL, value int ); --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; SELECT create_distributed_table('domain_partition_column_table', 'key'); create_distributed_table --------------------------------------------------------------------- (1 row) -RESET citus.enable_metadata_sync; PREPARE prepared_coercion_to_domain_insert(text) AS INSERT INTO domain_partition_column_table VALUES ($1, 1); EXECUTE prepared_coercion_to_domain_insert('test-1'); diff --git a/src/test/regress/expected/prepared_statements_4.out b/src/test/regress/expected/prepared_statements_4.out index 2d66e04eb..0dba296e8 100644 --- a/src/test/regress/expected/prepared_statements_4.out +++ b/src/test/regress/expected/prepared_statements_4.out @@ -20,16 +20,15 @@ SELECT key, value FROM text_partition_column_table ORDER BY key; test | 1 (7 rows) --- TODO: Uncomment tests below once domains are supported --- PREPARE prepared_coercion_to_domain_insert(text) AS --- INSERT INTO domain_partition_column_table VALUES ($1, 1); --- EXECUTE prepared_coercion_to_domain_insert('test-1'); --- EXECUTE prepared_coercion_to_domain_insert('test-2'); --- EXECUTE prepared_coercion_to_domain_insert('test-3'); --- EXECUTE prepared_coercion_to_domain_insert('test-4'); --- EXECUTE prepared_coercion_to_domain_insert('test-5'); --- EXECUTE prepared_coercion_to_domain_insert('test-6'); --- EXECUTE prepared_coercion_to_domain_insert('test-7'); +PREPARE prepared_coercion_to_domain_insert(text) AS + INSERT INTO domain_partition_column_table VALUES ($1, 1); +EXECUTE prepared_coercion_to_domain_insert('test-1'); +EXECUTE prepared_coercion_to_domain_insert('test-2'); +EXECUTE prepared_coercion_to_domain_insert('test-3'); +EXECUTE prepared_coercion_to_domain_insert('test-4'); +EXECUTE prepared_coercion_to_domain_insert('test-5'); +EXECUTE prepared_coercion_to_domain_insert('test-6'); +EXECUTE prepared_coercion_to_domain_insert('test-7'); PREPARE FOO AS INSERT INTO http_request ( site_id, ingest_time, url, request_country, ip_address, status_code, response_time_msec diff --git a/src/test/regress/expected/prepared_statements_create_load.out b/src/test/regress/expected/prepared_statements_create_load.out index 68d9baeb6..d1817f39f 100644 --- a/src/test/regress/expected/prepared_statements_create_load.out +++ b/src/test/regress/expected/prepared_statements_create_load.out @@ -65,19 +65,6 @@ SELECT create_distributed_table('text_partition_column_table', 'key'); -- Domain type columns can give issues -- and we use offset to prevent output diverging CREATE DOMAIN test_key AS text CHECK(VALUE ~ '^test-\d$'); --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. Also uncomment related tests on --- prepared_statements_4 test file. -SELECT run_command_on_workers($$ - CREATE DOMAIN "prepared statements".test_key AS text CHECK(VALUE ~ '^test-\d$') -$$) OFFSET 10000; - run_command_on_workers ---------------------------------------------------------------------- -(0 rows) - --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; CREATE TABLE domain_partition_column_table ( key test_key NOT NULL, value int @@ -88,7 +75,6 @@ SELECT create_distributed_table('domain_partition_column_table', 'key'); (1 row) -RESET citus.enable_metadata_sync; -- verify we re-evaluate volatile functions every time CREATE TABLE http_request ( site_id INT, diff --git a/src/test/regress/expected/shard_move_deferred_delete.out b/src/test/regress/expected/shard_move_deferred_delete.out index d878c18a4..ea85f9bfd 100644 --- a/src/test/regress/expected/shard_move_deferred_delete.out +++ b/src/test/regress/expected/shard_move_deferred_delete.out @@ -233,22 +233,27 @@ SET search_path TO shard_move_deferred_delete; SELECT master_move_shard_placement(20000001, 'localhost', :worker_2_port, 'localhost', :worker_1_port); ERROR: not enough empty space on node if the shard is moved, actual available space after move will be 108 bytes, desired available space after move is 850 bytes,estimated size increase on node after move is 8192 bytes. HINT: consider lowering citus.desired_percent_disk_available_after_move. --- Restore the original function -SELECT run_command_on_workers($cmd$ - CREATE OR REPLACE FUNCTION pg_catalog.citus_local_disk_space_stats( - OUT available_disk_size bigint, - OUT total_disk_size bigint) - RETURNS record - LANGUAGE C STRICT - AS 'citus', $$citus_local_disk_space_stats$$; - COMMENT ON FUNCTION pg_catalog.citus_local_disk_space_stats() - IS 'returns statistics on available disk space on the local node'; -$cmd$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,"CREATE FUNCTION") - (localhost,57638,t,"CREATE FUNCTION") -(2 rows) - +-- Restore the original function on workers +\c - - - :worker_1_port +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION pg_catalog.citus_local_disk_space_stats( +OUT available_disk_size bigint, +OUT total_disk_size bigint) +RETURNS record +LANGUAGE C STRICT +AS 'citus', $$citus_local_disk_space_stats$$; +COMMENT ON FUNCTION pg_catalog.citus_local_disk_space_stats() +IS 'returns statistics on available disk space on the local node'; +\c - - - :worker_2_port +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION pg_catalog.citus_local_disk_space_stats( +OUT available_disk_size bigint, +OUT total_disk_size bigint) +RETURNS record +LANGUAGE C STRICT +AS 'citus', $$citus_local_disk_space_stats$$; +COMMENT ON FUNCTION pg_catalog.citus_local_disk_space_stats() +IS 'returns statistics on available disk space on the local node'; +\c - - - :master_port DROP SCHEMA shard_move_deferred_delete CASCADE; -NOTICE: drop cascades to table t1 +NOTICE: drop cascades to table shard_move_deferred_delete.t1 diff --git a/src/test/regress/expected/tableam.out b/src/test/regress/expected/tableam.out index f82d0db90..50e9fcf91 100644 --- a/src/test/regress/expected/tableam.out +++ b/src/test/regress/expected/tableam.out @@ -12,6 +12,7 @@ SET citus.shard_count TO 4; create schema test_tableam; set search_path to test_tableam; SELECT public.run_command_on_coordinator_and_workers($Q$ + SET citus.enable_ddl_propagation TO off; CREATE FUNCTION fake_am_handler(internal) RETURNS table_am_handler AS 'citus' @@ -26,8 +27,6 @@ $Q$); -- Since Citus assumes access methods are part of the extension, make fake_am -- owned manually to be able to pass checks on Citus while distributing tables. ALTER EXTENSION citus ADD ACCESS METHOD fake_am; -NOTICE: Citus does not propagate adding/dropping member objects -HINT: You can add/drop the member objects on the workers as well. -- -- Hash distributed table using a non-default table access method -- diff --git a/src/test/regress/multi_1_schedule b/src/test/regress/multi_1_schedule index da7eb9c79..e95676e2c 100644 --- a/src/test/regress/multi_1_schedule +++ b/src/test/regress/multi_1_schedule @@ -317,7 +317,7 @@ test: ssl_by_default # TODO: After deprecating parameterless create_distributed_function combine # distributed_functions and function_propagation tests # --------- -test: distributed_types distributed_types_conflict disable_object_propagation distributed_types_xact_add_enum_value text_search +test: distributed_types distributed_types_conflict disable_object_propagation distributed_types_xact_add_enum_value text_search distributed_domain test: check_mx test: distributed_functions distributed_functions_conflict test: distributed_collations diff --git a/src/test/regress/spec/isolation_blocking_move_multi_shard_commands_on_mx.spec b/src/test/regress/spec/isolation_blocking_move_multi_shard_commands_on_mx.spec index 9d535d3df..df73e0bfc 100644 --- a/src/test/regress/spec/isolation_blocking_move_multi_shard_commands_on_mx.spec +++ b/src/test/regress/spec/isolation_blocking_move_multi_shard_commands_on_mx.spec @@ -3,6 +3,7 @@ setup { + SET citus.enable_metadata_sync TO off; CREATE OR REPLACE FUNCTION start_session_level_connection_to_node(text, integer) RETURNS void LANGUAGE C STRICT VOLATILE @@ -17,6 +18,7 @@ setup RETURNS void LANGUAGE C STRICT VOLATILE AS 'citus', $$stop_session_level_connection_to_node$$; + RESET citus.enable_metadata_sync; SELECT citus_internal.replace_isolation_tester_func(); SELECT citus_internal.refresh_isolation_tester_prepared_statement(); diff --git a/src/test/regress/spec/isolation_blocking_move_single_shard_commands_on_mx.spec b/src/test/regress/spec/isolation_blocking_move_single_shard_commands_on_mx.spec index 005c59e5b..74024a7c8 100644 --- a/src/test/regress/spec/isolation_blocking_move_single_shard_commands_on_mx.spec +++ b/src/test/regress/spec/isolation_blocking_move_single_shard_commands_on_mx.spec @@ -2,6 +2,7 @@ // so setting the corresponding shard here is useful setup { + SET citus.enable_ddl_propagation TO OFF; CREATE OR REPLACE FUNCTION start_session_level_connection_to_node(text, integer) RETURNS void LANGUAGE C STRICT VOLATILE @@ -16,6 +17,7 @@ setup RETURNS void LANGUAGE C STRICT VOLATILE AS 'citus', $$stop_session_level_connection_to_node$$; + RESET citus.enable_ddl_propagation; SELECT citus_internal.replace_isolation_tester_func(); SELECT citus_internal.refresh_isolation_tester_prepared_statement(); diff --git a/src/test/regress/spec/isolation_dump_global_wait_edges.spec b/src/test/regress/spec/isolation_dump_global_wait_edges.spec index 8debad290..d9328eddf 100644 --- a/src/test/regress/spec/isolation_dump_global_wait_edges.spec +++ b/src/test/regress/spec/isolation_dump_global_wait_edges.spec @@ -6,12 +6,14 @@ setup SELECT create_distributed_table('distributed_table', 'x'); INSERT INTO distributed_table VALUES (1,0); + SET citus.enable_ddl_propagation TO OFF; CREATE OR REPLACE FUNCTION get_adjacency_list_wait_graph(OUT transactionNumber int, OUT waitingTransactionNumbers cstring) RETURNS SETOF RECORD LANGUAGE C STRICT AS 'citus', $$get_adjacency_list_wait_graph$$; COMMENT ON FUNCTION get_adjacency_list_wait_graph(OUT transactionNumber int, OUT waitingTransactionNumbers cstring) IS 'returns flattened wait graph'; + RESET citus.enable_ddl_propagation; } teardown diff --git a/src/test/regress/spec/isolation_max_client_connections.spec b/src/test/regress/spec/isolation_max_client_connections.spec index d7d80ed76..ef801d433 100644 --- a/src/test/regress/spec/isolation_max_client_connections.spec +++ b/src/test/regress/spec/isolation_max_client_connections.spec @@ -11,10 +11,12 @@ setup GRANT USAGE ON SCHEMA public TO my_user; GRANT SELECT ON TABLE my_table TO my_user; + SET citus.enable_ddl_propagation TO OFF; CREATE FUNCTION make_external_connection_to_node(text,int,text,text) RETURNS void AS 'citus' LANGUAGE C STRICT; + RESET citus.enable_ddl_propagation; SELECT run_command_on_workers('ALTER SYSTEM SET citus.max_client_connections TO 1'); SELECT run_command_on_workers('SELECT pg_reload_conf()'); diff --git a/src/test/regress/spec/isolation_mx_common.include.spec b/src/test/regress/spec/isolation_mx_common.include.spec index 4749e334c..ed70908ac 100644 --- a/src/test/regress/spec/isolation_mx_common.include.spec +++ b/src/test/regress/spec/isolation_mx_common.include.spec @@ -2,6 +2,7 @@ // ready for testing MX functionalities. setup { + SET citus.enable_metadata_sync TO off; CREATE OR REPLACE FUNCTION start_session_level_connection_to_node(text, integer) RETURNS void LANGUAGE C STRICT VOLATILE @@ -26,6 +27,7 @@ setup RETURNS void LANGUAGE C STRICT VOLATILE AS 'citus', $$stop_session_level_connection_to_node$$; + RESET citus.enable_metadata_sync; SELECT citus_internal.replace_isolation_tester_func(); SELECT citus_internal.refresh_isolation_tester_prepared_statement(); diff --git a/src/test/regress/spec/isolation_rebalancer_deferred_drop.spec b/src/test/regress/spec/isolation_rebalancer_deferred_drop.spec index bf8a10eb7..f2e566936 100644 --- a/src/test/regress/spec/isolation_rebalancer_deferred_drop.spec +++ b/src/test/regress/spec/isolation_rebalancer_deferred_drop.spec @@ -2,7 +2,7 @@ // so setting the corresponding shard here is useful setup { - + SET citus.enable_metadata_sync TO off; CREATE OR REPLACE FUNCTION run_try_drop_marked_shards() RETURNS VOID AS 'citus' @@ -31,6 +31,7 @@ CREATE OR REPLACE PROCEDURE isolation_cleanup_orphaned_shards() AS 'citus', $$isolation_cleanup_orphaned_shards$$; COMMENT ON PROCEDURE isolation_cleanup_orphaned_shards() IS 'cleanup orphaned shards'; + RESET citus.enable_metadata_sync; SET citus.next_shard_id to 120000; SET citus.shard_count TO 8; diff --git a/src/test/regress/spec/isolation_replicate_reference_tables_to_coordinator.spec b/src/test/regress/spec/isolation_replicate_reference_tables_to_coordinator.spec index f55f869dd..bad22d7b9 100644 --- a/src/test/regress/spec/isolation_replicate_reference_tables_to_coordinator.spec +++ b/src/test/regress/spec/isolation_replicate_reference_tables_to_coordinator.spec @@ -94,7 +94,8 @@ step "s2-view-worker" ('%dump_local_%'), ('%citus_internal_local_blocked_processes%'), ('%add_node%'), - ('%csa_from_one_node%')) + ('%csa_from_one_node%'), + ('%pg_locks%')) AND is_worker_query = true AND backend_type = 'client backend' AND query != '' diff --git a/src/test/regress/sql/binary_protocol.sql b/src/test/regress/sql/binary_protocol.sql index 2e721d33a..8c9761313 100644 --- a/src/test/regress/sql/binary_protocol.sql +++ b/src/test/regress/sql/binary_protocol.sql @@ -46,8 +46,8 @@ CREATE TYPE nested_composite_type AS ( a composite_type, b composite_type ); -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.composite_type_domain AS binary_protocol.composite_type$$); -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.nested_composite_type_domain AS binary_protocol.nested_composite_type$$); +CREATE DOMAIN binary_protocol.composite_type_domain AS binary_protocol.composite_type; +CREATE DOMAIN binary_protocol.nested_composite_type_domain AS binary_protocol.nested_composite_type; INSERT INTO composite_type_table(col) VALUES ((1, 2)::composite_type); @@ -74,8 +74,8 @@ CREATE TYPE binaryless_composite_type AS ( b aclitem ); -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.binaryless_domain AS aclitem$$); -select run_command_on_master_and_workers($$CREATE DOMAIN binary_protocol.binaryless_composite_domain AS binary_protocol.binaryless_composite_type$$); +CREATE DOMAIN binary_protocol.binaryless_domain AS aclitem; +CREATE DOMAIN binary_protocol.binaryless_composite_domain AS binary_protocol.binaryless_composite_type; INSERT INTO binaryless_builtin VALUES ('user postgres=r/postgres', 'test'); SELECT col1 FROM binaryless_builtin; diff --git a/src/test/regress/sql/citus_local_table_triggers.sql b/src/test/regress/sql/citus_local_table_triggers.sql index 57394b306..95b700a43 100644 --- a/src/test/regress/sql/citus_local_table_triggers.sql +++ b/src/test/regress/sql/citus_local_table_triggers.sql @@ -27,7 +27,6 @@ ALTER TABLE citus_local_table ADD CONSTRAINT fkey_to_dummy_1 FOREIGN KEY (value) BEGIN; CREATE TABLE distributed_table(value int); - SELECT create_distributed_table('distributed_table', 'value'); CREATE FUNCTION insert_42() RETURNS trigger AS $insert_42$ BEGIN INSERT INTO distributed_table VALUES (42); @@ -35,6 +34,8 @@ BEGIN; END; $insert_42$ LANGUAGE plpgsql; + SELECT create_distributed_table('distributed_table', 'value'); + CREATE TRIGGER insert_42_trigger AFTER DELETE ON citus_local_table FOR EACH ROW EXECUTE FUNCTION insert_42(); diff --git a/src/test/regress/sql/coordinator_evaluation.sql b/src/test/regress/sql/coordinator_evaluation.sql index 047d19c3f..10bea8842 100644 --- a/src/test/regress/sql/coordinator_evaluation.sql +++ b/src/test/regress/sql/coordinator_evaluation.sql @@ -214,21 +214,8 @@ SELECT count(*) FROM coordinator_evaluation_table_2 WHERE key = 101; CREATE TYPE comptype_int as (int_a int); CREATE DOMAIN domain_comptype_int AS comptype_int CHECK ((VALUE).int_a > 0); --- citus does not propagate domain types --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. -SELECT run_command_on_workers( -$$ - CREATE DOMAIN coordinator_evaluation.domain_comptype_int AS coordinator_evaluation.comptype_int CHECK ((VALUE).int_a > 0) -$$); - CREATE TABLE reference_table(column_a coordinator_evaluation.domain_comptype_int); - --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; SELECT create_reference_table('reference_table'); -RESET citus.enable_metadata_sync; INSERT INTO reference_table (column_a) VALUES ('(1)'); INSERT INTO reference_table (column_a) VALUES ('(2)'), ('(3)'); diff --git a/src/test/regress/sql/distributed_domain.sql b/src/test/regress/sql/distributed_domain.sql new file mode 100644 index 000000000..c75d89216 --- /dev/null +++ b/src/test/regress/sql/distributed_domain.sql @@ -0,0 +1,487 @@ +CREATE SCHEMA distributed_domain; +SET search_path TO distributed_domain; +SET citus.next_shard_id TO 93631000; + +-- verify domain is not already present on workers +SELECT * FROM run_command_on_workers($$ + SELECT 'distributed_domain.age'::regtype; +$$) ORDER BY 1,2; +CREATE DOMAIN age AS int CHECK( VALUE >= 0 ); +-- check domain exists on workers to proof the domain got propagated +SELECT * FROM run_command_on_workers($$ + SELECT 'distributed_domain.age'::regtype; +$$) ORDER BY 1,2; + +-- verify the constraint triggers when operations that conflict are pushed to the shards +CREATE TABLE foo (a int); +SELECT create_distributed_table('foo', 'a', shard_count => 2); +CREATE TABLE bar (a age); +SELECT create_distributed_table('bar', 'a', shard_count => 2); +INSERT INTO foo (a) VALUES (-1); -- foo can have negative values +INSERT INTO bar (a) SELECT a FROM foo; -- bar cannot + +DROP TABLE bar; -- need to drop this one directly, there is currently a bug in drop schema cascade when it drops both a table and the type of the distribution column at the same time + +-- create a domain that is not propagated +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN us_postal_code AS TEXT + CHECK( + VALUE ~ '^\d{5}$' + OR VALUE ~ '^\d{5}-\d{4}$' + ); +RESET citus.enable_ddl_propagation; + +-- and use in distributed table to trigger domain propagation +CREATE TABLE us_snail_addy ( + address_id SERIAL PRIMARY KEY, + street1 TEXT NOT NULL, + street2 TEXT, + street3 TEXT, + city TEXT NOT NULL, + postal us_postal_code NOT NULL +); +SELECT create_distributed_table('us_snail_addy', 'address_id'); + +-- defaults are marked as a constraint on the statement, which makes it interesting for deparsing, so some extensive +-- test coverage to make sure it works in all strange cases. +CREATE SCHEMA distributed_domain_constraints; +SET search_path TO distributed_domain_constraints; +CREATE DOMAIN with_default AS int DEFAULT 0; +CREATE DOMAIN age_with_default AS int DEFAULT 0 CHECK (value > 0); +CREATE DOMAIN age_with_default2 AS int CHECK (value > 0) DEFAULT 0; +CREATE DOMAIN age_with_default3 AS int CHECK (value > 0) DEFAULT NULL; +CREATE DOMAIN age_with_default4 AS int NOT NULL CHECK (value > 0) DEFAULT NULL; + +-- test casting with worker queries +-- should simply work, has no check constraints +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.with_default; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.with_default; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.with_default; +$$) ORDER BY 1,2; + +-- has a constraint where the number needs to be greater than 0 +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default; +$$) ORDER BY 1,2; + +-- has a constraint where the number needs to be greater than 0 +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default2; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default2; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default2; +$$) ORDER BY 1,2; + +-- has a constraint where the number needs to be greater than 0 +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default3; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default3; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default3; +$$) ORDER BY 1,2; + +-- has a constraint where the number needs to be greater than 0 and not null +SELECT * FROM run_command_on_workers($$ + SELECT NULL::distributed_domain_constraints.age_with_default4; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 0::distributed_domain_constraints.age_with_default4; +$$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ + SELECT 1::distributed_domain_constraints.age_with_default4; +$$) ORDER BY 1,2; + +-- test usage in distributed tables +-- we use all domains defined earlier and insert all possible values, NULL, default, non-violation, violation. Some of +-- the default values will violate a constraint. +-- Based on the constraints on the domain we see different errors and will end up with a final set of data stored. +CREATE TABLE use_default (a int, b with_default); +SELECT create_distributed_table('use_default', 'a', shard_count => 2); +INSERT INTO use_default (a, b) VALUES (0, NULL); +INSERT INTO use_default (a) VALUES (1); +INSERT INTO use_default (a, b) VALUES (2, 1); +INSERT INTO use_default (a, b) VALUES (3, -1); +SELECT * FROM use_default ORDER BY a; + +CREATE TABLE use_age_default (a int, b age_with_default); +SELECT create_distributed_table('use_age_default', 'a', shard_count => 2); +INSERT INTO use_age_default (a, b) VALUES (0, NULL); +INSERT INTO use_age_default (a) VALUES (1); +INSERT INTO use_age_default (a, b) VALUES (2, 1); +INSERT INTO use_age_default (a, b) VALUES (3, -1); +-- also load some data with copy to verify coercions. +\COPY use_age_default FROM STDIN DELIMITER AS ','; +4, -1 +\. +\COPY use_age_default FROM STDIN DELIMITER AS ','; +5, 1 +\. +SELECT * FROM use_age_default ORDER BY a; + +CREATE TABLE use_age_default2 (a int, b age_with_default2); +SELECT create_distributed_table('use_age_default2', 'a', shard_count => 2); +INSERT INTO use_age_default2 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default2 (a) VALUES (1); +INSERT INTO use_age_default2 (a, b) VALUES (2, 1); +INSERT INTO use_age_default2 (a, b) VALUES (3, -1); +SELECT * FROM use_age_default2 ORDER BY a; + +CREATE TABLE use_age_default3 (a int, b age_with_default3); +SELECT create_distributed_table('use_age_default3', 'a', shard_count => 2); +INSERT INTO use_age_default3 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default3 (a) VALUES (1); +INSERT INTO use_age_default3 (a, b) VALUES (2, 1); +INSERT INTO use_age_default3 (a, b) VALUES (3, -1); +SELECT * FROM use_age_default3 ORDER BY a; + +CREATE TABLE use_age_default4 (a int, b age_with_default4); +SELECT create_distributed_table('use_age_default4', 'a', shard_count => 2); +INSERT INTO use_age_default4 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default4 (a) VALUES (1); +INSERT INTO use_age_default4 (a, b) VALUES (2, 1); +INSERT INTO use_age_default4 (a, b) VALUES (3, -1); +SELECT * FROM use_age_default4 ORDER BY a; + +SET client_min_messages TO warning; +DROP SCHEMA distributed_domain_constraints CASCADE; +RESET client_min_messages; + +-- same tests as above, with with just in time propagation of domains +CREATE SCHEMA distributed_domain_constraints; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN with_default AS int DEFAULT 0; +CREATE DOMAIN age_with_default AS int DEFAULT 0 CHECK (value > 0); +CREATE DOMAIN age_with_default2 AS int CHECK (value > 0) DEFAULT 0; +CREATE DOMAIN age_with_default3 AS int CHECK (value > 0) DEFAULT NULL; +CREATE DOMAIN age_with_default4 AS int NOT NULL CHECK (value > 0) DEFAULT NULL; +RESET citus.enable_ddl_propagation; + +-- use all domains in tables to get them propagated +CREATE TABLE use_default (a int, b with_default); +SELECT create_distributed_table('use_default', 'a', shard_count => 2); +INSERT INTO use_default (a, b) VALUES (0, NULL); +INSERT INTO use_default (a) VALUES (1); +INSERT INTO use_default (a, b) VALUES (2, 1); +INSERT INTO use_default (a, b) VALUES (3, -1); +SELECT * FROM use_default ORDER BY a; + +CREATE TABLE use_age_default (a int, b age_with_default); +SELECT create_distributed_table('use_age_default', 'a', shard_count => 2); +INSERT INTO use_age_default (a, b) VALUES (0, NULL); +INSERT INTO use_age_default (a) VALUES (1); +INSERT INTO use_age_default (a, b) VALUES (2, 1); +INSERT INTO use_age_default (a, b) VALUES (3, -1); +SELECT * FROM use_age_default ORDER BY a; + +CREATE TABLE use_age_default2 (a int, b age_with_default2); +SELECT create_distributed_table('use_age_default2', 'a', shard_count => 2); +INSERT INTO use_age_default2 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default2 (a) VALUES (1); +INSERT INTO use_age_default2 (a, b) VALUES (2, 1); +INSERT INTO use_age_default2 (a, b) VALUES (3, -1); +SELECT * FROM use_age_default2 ORDER BY a; + +CREATE TABLE use_age_default3 (a int, b age_with_default3); +SELECT create_distributed_table('use_age_default3', 'a', shard_count => 2); +INSERT INTO use_age_default3 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default3 (a) VALUES (1); +INSERT INTO use_age_default3 (a, b) VALUES (2, 1); +INSERT INTO use_age_default3 (a, b) VALUES (3, -1); +SELECT * FROM use_age_default3 ORDER BY a; + +CREATE TABLE use_age_default4 (a int, b age_with_default4); +SELECT create_distributed_table('use_age_default4', 'a', shard_count => 2); +INSERT INTO use_age_default4 (a, b) VALUES (0, NULL); +INSERT INTO use_age_default4 (a) VALUES (1); +INSERT INTO use_age_default4 (a, b) VALUES (2, 1); +INSERT INTO use_age_default4 (a, b) VALUES (3, -1); +SELECT * FROM use_age_default4 ORDER BY a; + +-- clean up +SET client_min_messages TO warning; +DROP SCHEMA distributed_domain_constraints CASCADE; +RESET client_min_messages; + +CREATE SCHEMA postgres_domain_examples; +SET search_path TO postgres_domain_examples; + +-- make sure the function gets automatically propagated when we propagate the domain +SET citus.enable_ddl_propagation TO off; +create function sql_is_distinct_from(anyelement, anyelement) + returns boolean language sql + as 'select $1 is distinct from $2 limit 1'; +RESET citus.enable_ddl_propagation; + +CREATE DOMAIN inotnull int + CHECK (sql_is_distinct_from(value, null)); + +SELECT * FROM run_command_on_workers($$ SELECT 1::postgres_domain_examples.inotnull; $$); +SELECT * FROM run_command_on_workers($$ SELECT null::postgres_domain_examples.inotnull; $$); + +-- create a domain with sql function as a default value +SET citus.enable_ddl_propagation TO off; +create function random_between(min int, max int) + returns int language sql + as 'SELECT round(random()*($2-$1))+$1;'; +RESET citus.enable_ddl_propagation; + +-- this verifies the function in the default expression is found and distributed, otherwise the creation of the domain would fail on the workers. +CREATE DOMAIN with_random_default int DEFAULT random_between(100, 200); + +SET client_min_messages TO warning; +DROP SCHEMA postgres_domain_examples CASCADE; +RESET client_min_messages; + +SET search_path TO distributed_domain; + +-- verify drops are propagated +CREATE DOMAIN will_drop AS text DEFAULT 'foo'; +DROP DOMAIN will_drop; +-- verify domain is dropped from workers +SELECT * FROM run_command_on_workers($$ SELECT 'dropped?'::distributed_domain.will_drop; $$); + +-- verify the type modifiers are deparsed correctly, both for direct propagation as well as on demand propagation +CREATE DOMAIN varcharmod AS varchar(3); +SELECT * FROM run_command_on_workers($$ SELECT '12345'::distributed_domain.varcharmod; $$) ORDER BY 1,2; + +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN varcharmod_ondemand AS varchar(3); +RESET citus.enable_ddl_propagation; +CREATE TABLE use_varcharmod_ondemand (a int, b varcharmod_ondemand); +SELECT create_distributed_table('use_varcharmod_ondemand', 'a'); +-- epxected error due to value being too long for varchar(3) +INSERT INTO use_varcharmod_ondemand VALUES (1,'12345'); +SELECT * FROM run_command_on_workers($$ SELECT '12345'::distributed_domain.varcharmod_ondemand; $$) ORDER BY 1,2; + +-- section testing default altering +CREATE DOMAIN alter_default AS text DEFAULT 'foo'; +CREATE TABLE use_alter_default (a int, b alter_default); +SELECT create_distributed_table('use_alter_default', 'a', shard_count => 4); +INSERT INTO use_alter_default (a) VALUES (1); +ALTER DOMAIN alter_default SET DEFAULT 'bar'; +INSERT INTO use_alter_default (a) VALUES (2); +ALTER DOMAIN alter_default DROP DEFAULT; +INSERT INTO use_alter_default (a) VALUES (3); +SELECT * FROM use_alter_default ORDER BY 1,2; + +-- add new dependency while adding default +CREATE DOMAIN add_default_with_function AS int; +SET citus.enable_ddl_propagation TO off; +create function random_between(min int, max int) + returns int language sql + as 'SELECT round(random()*($2-$1))+$1;'; +RESET citus.enable_ddl_propagation; +ALTER DOMAIN add_default_with_function SET DEFAULT random_between(100, 200); +CREATE TABLE use_add_default_with_function (a int, b add_default_with_function); +SELECT create_distributed_table('use_add_default_with_function', 'a', shard_count => 4); +INSERT INTO use_add_default_with_function (a) VALUES (1); + +-- altering NULL/NOT NULL +CREATE DOMAIN alter_null AS int; +CREATE TABLE use_alter_null (a int, b alter_null); +SELECT create_distributed_table('use_alter_null', 'a'); +INSERT INTO use_alter_null (a) VALUES (1); +ALTER DOMAIN alter_null SET NOT NULL; +TRUNCATE use_alter_null; +ALTER DOMAIN alter_null SET NOT NULL; +INSERT INTO use_alter_null (a) VALUES (2); +ALTER DOMAIN alter_null DROP NOT NULL; +INSERT INTO use_alter_null (a) VALUES (3); +SELECT * FROM use_alter_null ORDER BY 1; + +-- testing adding/dropping constraints +SET citus.enable_ddl_propagation TO off; +create function sql_is_distinct_from(anyelement, anyelement) + returns boolean language sql + as 'select $1 is distinct from $2 limit 1'; +RESET citus.enable_ddl_propagation; + +CREATE DOMAIN alter_add_constraint int; +ALTER DOMAIN alter_add_constraint ADD CONSTRAINT check_distinct CHECK (sql_is_distinct_from(value, null)); +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.alter_add_constraint; $$); +SELECT * FROM run_command_on_workers($$ SELECT null::distributed_domain.alter_add_constraint; $$); + +ALTER DOMAIN alter_add_constraint DROP CONSTRAINT check_distinct; +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.alter_add_constraint; $$); +SELECT * FROM run_command_on_workers($$ SELECT null::distributed_domain.alter_add_constraint; $$); +ALTER DOMAIN alter_add_constraint DROP CONSTRAINT IF EXISTS check_distinct; + +ALTER DOMAIN alter_add_constraint ADD CONSTRAINT check_distinct CHECK (sql_is_distinct_from(value, null)); +ALTER DOMAIN alter_add_constraint RENAME CONSTRAINT check_distinct TO check_distinct_renamed; +ALTER DOMAIN alter_add_constraint DROP CONSTRAINT check_distinct_renamed; + +-- test validating invalid constraints +CREATE DOMAIN age_invalid AS int NOT NULL DEFAULT 0; +CREATE TABLE use_age_invalid (a int, b age_invalid); +SELECT create_distributed_table('use_age_invalid', 'a', shard_count => 4); +INSERT INTO use_age_invalid VALUES (1,1), (2, 2), (3, 0), (4, -1); +ALTER DOMAIN age_invalid ADD CONSTRAINT check_age_positive CHECK (value>=0) NOT VALID; +-- should fail, even though constraint is not valid +INSERT INTO use_age_invalid VALUES (5,-1); +-- reading violating data of an non-valid constraint errors in citus +SELECT * FROM use_age_invalid ORDER BY 1; +-- should fail since there is data in the table that violates the check +ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive; +DELETE FROM use_age_invalid WHERE b < 0; +-- should succeed now since the violating data has been removed +ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive; +-- still fails for constraint +INSERT INTO use_age_invalid VALUES (5,-1); +SELECT * FROM use_age_invalid ORDER BY 1; +-- verify we can validate a constraint that is already validated, can happen when we add a node while a domain constraint was not validated +ALTER DOMAIN age_invalid VALIDATE CONSTRAINT check_age_positive; + +-- test changing the owner of a domain +SET client_min_messages TO error; +SELECT 1 FROM run_command_on_workers($$ CREATE ROLE domain_owner; $$); +CREATE ROLE domain_owner; +RESET client_min_messages; + +CREATE DOMAIN alter_domain_owner AS int; +ALTER DOMAIN alter_domain_owner OWNER TO domain_owner; + +SELECT u.rolname +FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) +WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; + +SELECT * FROM run_command_on_workers($$ + SELECT u.rolname + FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) + WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; +$$) ORDER BY 1,2; + +DROP DOMAIN alter_domain_owner; + +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN alter_domain_owner AS int; +ALTER DOMAIN alter_domain_owner OWNER TO domain_owner; +RESET citus.enable_ddl_propagation; + +CREATE TABLE use_alter_domain_owner (a int, b alter_domain_owner); +SELECT create_distributed_table('use_alter_domain_owner', 'a', shard_count => 4); + +SELECT u.rolname +FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) +WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; + +SELECT * FROM run_command_on_workers($$ + SELECT u.rolname + FROM pg_type t + JOIN pg_roles u + ON (t.typowner = u.oid) + WHERE t.oid = 'distributed_domain.alter_domain_owner'::regtype; +$$) ORDER BY 1,2; + +-- rename the domain +ALTER DOMAIN alter_domain_owner RENAME TO renamed_domain; +SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.renamed_domain; $$) ORDER BY 1,2; + +-- move schema +SET citus.enable_ddl_propagation TO off; +CREATE SCHEMA distributed_domain_moved; +RESET citus.enable_ddl_propagation; +ALTER DOMAIN renamed_domain SET SCHEMA distributed_domain_moved; + +-- test collation +CREATE COLLATION german_phonebook (provider = icu, locale = 'de-u-co-phonebk'); + +CREATE DOMAIN with_collation AS text COLLATE german_phonebook NOT NULL; +SELECT run_command_on_workers($$ SELECT typcollation::regcollation FROM pg_type WHERE oid = 'distributed_domain.with_collation'::regtype; $$); +DROP DOMAIN with_collation; + +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN with_collation AS text COLLATE german_phonebook NOT NULL; +RESET citus.enable_ddl_propagation; + +CREATE TABLE use_with_collation (a int, b with_collation); +SELECT create_reference_table('use_with_collation'); +SELECT run_command_on_workers($$ SELECT typcollation::regcollation FROM pg_type WHERE oid = 'distributed_domain.with_collation'::regtype; $$); + +INSERT INTO use_with_collation VALUES (1, U&'\00E4sop'), (2, 'Vossr'); +SELECT * FROM use_with_collation WHERE b < 'b'; + +-- test domain backed by array +CREATE DOMAIN domain_array AS int[] NOT NULL CHECK (array_length(value,1) >= 2); +SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.domain_array; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT ARRAY[1]::distributed_domain.domain_array; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT ARRAY[1,2]::distributed_domain.domain_array; $$) ORDER BY 1,2; + +DROP DOMAIN domain_array; + +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN domain_array AS int[] NOT NULL CHECK (array_length(value,1) >= 2); +RESET citus.enable_ddl_propagation; + +CREATE TABLE use_domain_array (a int, b domain_array); +SELECT create_distributed_table('use_domain_array', 'a'); +INSERT INTO use_domain_array VALUES (1, NULL); +INSERT INTO use_domain_array VALUES (2, ARRAY[1]); +INSERT INTO use_domain_array VALUES (3, ARRAY[1,2]); +SELECT * FROM use_domain_array ORDER BY 1; + +-- add nameless constraint +CREATE DOMAIN nameless_constraint AS int; +ALTER DOMAIN nameless_constraint ADD CHECK (value > 0); +SELECT * FROM run_command_on_workers($$ SELECT NULL::distributed_domain.nameless_constraint; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.nameless_constraint; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT (-1)::distributed_domain.nameless_constraint; $$) ORDER BY 1,2; + +-- Test domains over domains +create domain vchar4 varchar(4); +create domain dinter vchar4 check (substring(VALUE, 1, 1) = 'x'); +create domain dtop dinter check (substring(VALUE, 2, 1) = '1'); +create table dtest(f1 dtop, id bigserial); +SELECT create_distributed_table('dtest', 'id'); + +insert into dtest values('x123'); +insert into dtest values('123'); +insert into dtest values('x234'); + +DROP TABLE dtest; + +DROP DOMAIN IF EXISTS dtop; +DROP DOMAIN vchar4; +DROP DOMAIN vchar4 CASCADE; + +-- drop multiple domains at once, for which one is not distributed +CREATE DOMAIN domain1 AS int; +CREATE DOMAIN domain2 AS text; +SET citus.enable_ddl_propagation TO off; +CREATE DOMAIN domain3 AS text; +RESET citus.enable_ddl_propagation; + +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.domain1; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain2; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain3; $$) ORDER BY 1,2; + +DROP DOMAIN domain1, domain2, domain3; + +SELECT * FROM run_command_on_workers($$ SELECT 1::distributed_domain.domain1; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain2; $$) ORDER BY 1,2; +SELECT * FROM run_command_on_workers($$ SELECT '1'::distributed_domain.domain3; $$) ORDER BY 1,2; + +SET client_min_messages TO warning; +DROP SCHEMA distributed_domain, distributed_domain_moved CASCADE; diff --git a/src/test/regress/sql/distributed_triggers.sql b/src/test/regress/sql/distributed_triggers.sql index 33ecdbb8c..9acb1434c 100644 --- a/src/test/regress/sql/distributed_triggers.sql +++ b/src/test/regress/sql/distributed_triggers.sql @@ -175,6 +175,8 @@ DROP TRIGGER bad_shardkey_record_change_trigger ON data; CREATE OR REPLACE FUNCTION remote_shardkey_record_change() RETURNS trigger +SET search_path = 'distributed_triggers' +LANGUAGE plpgsql AS $$ DECLARE last_change_id bigint; @@ -182,14 +184,17 @@ BEGIN UPDATE distributed_triggers.data_changes SET operation_type = TG_OP; RETURN NULL; END; -$$ LANGUAGE plpgsql; +$$; CREATE TRIGGER remote_shardkey_record_change_trigger AFTER INSERT OR UPDATE OR DELETE ON data FOR EACH ROW EXECUTE FUNCTION distributed_triggers.remote_shardkey_record_change(); CREATE FUNCTION insert_document(key text, id text) -RETURNS void LANGUAGE plpgsql AS $fn$ +RETURNS void +LANGUAGE plpgsql +SET search_path = 'distributed_triggers' +AS $fn$ BEGIN INSERT INTO distributed_triggers.data VALUES (key, id, '{"id1":"id2"}'); DELETE FROM distributed_triggers.data where shard_key_value = key; @@ -465,8 +470,8 @@ SELECT tgrelid::regclass::text, tgname FROM pg_trigger WHERE tgname like 'insert SELECT run_command_on_workers($$SELECT count(*) FROM pg_trigger WHERE tgname like 'insert_99_trigger%'$$); RESET client_min_messages; -SET citus.enable_unsafe_triggers TO false; -SELECT run_command_on_workers('ALTER SYSTEM SET citus.enable_unsafe_triggers TO false;'); +RESET citus.enable_unsafe_triggers; +SELECT run_command_on_workers('ALTER SYSTEM RESET citus.enable_unsafe_triggers;'); SELECT run_command_on_workers('SELECT pg_reload_conf();'); SET citus.log_remote_commands TO off; diff --git a/src/test/regress/sql/distributed_types.sql b/src/test/regress/sql/distributed_types.sql index 085518ffa..f1ada4e3a 100644 --- a/src/test/regress/sql/distributed_types.sql +++ b/src/test/regress/sql/distributed_types.sql @@ -260,20 +260,8 @@ UPDATE field_indirection_test_2 SET (ct2_col.text_1, ct1_col.int_2) = ('text2', CREATE TYPE two_ints as (if1 int, if2 int); CREATE DOMAIN domain AS two_ints CHECK ((VALUE).if1 > 0); --- citus does not propagate domain objects --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. -SELECT run_command_on_workers( -$$ - CREATE DOMAIN type_tests.domain AS type_tests.two_ints CHECK ((VALUE).if1 > 0); -$$); CREATE TABLE domain_indirection_test (f1 int, f3 domain, domain_array domain[]); - --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; SELECT create_distributed_table('domain_indirection_test', 'f1'); -RESET citus.enable_metadata_sync; -- not supported (field indirection to underlying composite type) INSERT INTO domain_indirection_test (f1,f3.if1, f3.if2) VALUES (0, 1, 2); diff --git a/src/test/regress/sql/function_propagation.sql b/src/test/regress/sql/function_propagation.sql index eab7927e4..d4d3a7322 100644 --- a/src/test/regress/sql/function_propagation.sql +++ b/src/test/regress/sql/function_propagation.sql @@ -192,6 +192,7 @@ $$; -- Function as a default column BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_def() RETURNS int LANGUAGE plpgsql AS @@ -201,7 +202,6 @@ BEGIN; END; $$; - -- Function shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_def'::regproc::oid; CREATE TABLE table_to_prop_func(id int, col_1 int default func_in_transaction_def()); @@ -217,6 +217,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Multiple functions as a default column BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_1() RETURNS int LANGUAGE plpgsql AS @@ -235,7 +236,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_1'::regproc::oid; SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_2'::regproc::oid; @@ -275,8 +275,8 @@ COMMIT; -- Adding a column with default value should propagate the function BEGIN; CREATE TABLE table_to_prop_func_4(id int); - SELECT create_distributed_table('table_to_prop_func_4', 'id'); + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_4() RETURNS int LANGUAGE plpgsql AS @@ -286,7 +286,8 @@ BEGIN; END; $$; - -- Function shouldn't be propagated within transaction + SELECT create_distributed_table('table_to_prop_func_4', 'id'); + SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_4'::regproc::oid; ALTER TABLE table_to_prop_func_4 ADD COLUMN col_1 int default function_propagation_schema.func_in_transaction_4(); @@ -303,6 +304,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas BEGIN; CREATE TABLE non_dist_table_for_function(id int); + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION non_dist_func(col_1 non_dist_table_for_function) RETURNS int LANGUAGE plpgsql AS @@ -322,6 +324,7 @@ ROLLBACK; -- Adding multiple columns with default values should propagate the function BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_5() RETURNS int LANGUAGE plpgsql AS @@ -340,8 +343,6 @@ BEGIN; END; $$; - - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_5'::regproc::oid; SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_6'::regproc::oid; @@ -359,6 +360,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Adding a constraint with function check should propagate the function BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_7(param_1 int) RETURNS boolean LANGUAGE plpgsql AS @@ -368,7 +370,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_7'::regproc::oid; CREATE TABLE table_to_prop_func_6(id int, col_1 int check (function_propagation_schema.func_in_transaction_7(col_1))); @@ -384,6 +385,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Adding a constraint with multiple functions check should propagate the function BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_8(param_1 int) RETURNS boolean LANGUAGE plpgsql AS @@ -402,7 +404,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_8'::regproc::oid; SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_9'::regproc::oid; @@ -424,6 +425,7 @@ BEGIN; CREATE TABLE table_to_prop_func_8(id int, col_1 int); SELECT create_distributed_table('table_to_prop_func_8', 'id'); + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_10(param_1 int) RETURNS boolean LANGUAGE plpgsql AS @@ -433,7 +435,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_10'::regproc::oid; ALTER TABLE table_to_prop_func_8 ADD CONSTRAINT col1_check CHECK (function_propagation_schema.func_in_transaction_10(col_1)); @@ -468,7 +469,7 @@ COMMIT; -- Show that function as a part of generated always is supporte BEGIN; - + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION non_sense_func_for_generated_always() RETURNS int LANGUAGE plpgsql IMMUTABLE AS @@ -478,7 +479,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.non_sense_func_for_generated_always'::regproc::oid; CREATE TABLE people ( @@ -495,6 +495,7 @@ COMMIT; -- Show that functions depending table via rule are also distributed BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_for_rule() RETURNS int LANGUAGE plpgsql STABLE AS @@ -503,8 +504,8 @@ BEGIN; return 4; END; $$; + RESET citus.create_object_propagation; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_for_rule'::regproc::oid; CREATE TABLE table_1_for_rule(id int, col_1 int); @@ -524,7 +525,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Show that functions as partitioning functions are supported BEGIN; - + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION non_sense_func_for_partitioning(int) RETURNS int LANGUAGE plpgsql IMMUTABLE AS @@ -534,7 +535,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.non_sense_func_for_partitioning'::regproc::oid; CREATE TABLE partitioned_table_to_test_func_prop(id INT, a INT) PARTITION BY RANGE (non_sense_func_for_partitioning(id)); @@ -551,6 +551,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Test function dependency on citus local table BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_for_local_table() RETURNS int LANGUAGE plpgsql AS @@ -560,7 +561,6 @@ BEGIN; END; $$; - -- Function shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_for_local_table'::regproc::oid; CREATE TABLE citus_local_table_to_test_func(l1 int DEFAULT func_in_transaction_for_local_table()); @@ -573,6 +573,7 @@ ROLLBACK; -- Show that having a function dependency on exlude also works BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION exclude_bool_func() RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS @@ -582,7 +583,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.exclude_bool_func'::regproc::oid; CREATE TABLE exclusion_func_prop_table (id int, EXCLUDE USING btree (id WITH =) WHERE (exclude_bool_func())); @@ -598,6 +598,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Show that having a function dependency for index also works BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_for_index_predicate(col_1 int) RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS @@ -607,7 +608,6 @@ BEGIN; END; $$; - -- Functions shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_for_index_predicate'::regproc::oid; CREATE TABLE table_to_check_func_index_dep (id int, col_2 int); @@ -657,6 +657,7 @@ SELECT * FROM run_command_on_workers($$SELECT pg_identify_object_as_address(clas -- Test function with SQL language and sequence dependency BEGIN; + SET LOCAL citus.create_object_propagation TO deferred; CREATE OR REPLACE FUNCTION func_in_transaction_def_with_seq(val bigint) RETURNS bigint LANGUAGE SQL AS @@ -671,7 +672,6 @@ BEGIN; SELECT func_in_transaction_def_with_seq(val); $$; - -- Function shouldn't be propagated within transaction SELECT pg_identify_object_as_address(classid, objid, objsubid) from pg_catalog.pg_dist_object where objid = 'function_propagation_schema.func_in_transaction_def_with_seq'::regproc::oid; CREATE SEQUENCE myseq; diff --git a/src/test/regress/sql/metadata_sync_helpers.sql b/src/test/regress/sql/metadata_sync_helpers.sql index a2b4ccf8d..91641f22c 100644 --- a/src/test/regress/sql/metadata_sync_helpers.sql +++ b/src/test/regress/sql/metadata_sync_helpers.sql @@ -424,6 +424,7 @@ BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED; SET application_name to 'citus_internal gpid=10000000001'; \set VERBOSITY terse + SET citus.enable_ddl_propagation TO OFF; CREATE FUNCTION distribution_test_function(int) RETURNS int AS $$ SELECT $1 $$ LANGUAGE SQL; diff --git a/src/test/regress/sql/multi_alter_table_add_constraints.sql b/src/test/regress/sql/multi_alter_table_add_constraints.sql index 3e80b09c0..612e4d22b 100644 --- a/src/test/regress/sql/multi_alter_table_add_constraints.sql +++ b/src/test/regress/sql/multi_alter_table_add_constraints.sql @@ -518,6 +518,13 @@ SELECT (run_command_on_workers($$ ORDER BY 1,2,3,4; +CREATE TABLE alter_add_unique(x int, y int); +CREATE UNIQUE INDEX CONCURRENTLY alter_unique_idx ON alter_add_unique(x); +SELECT create_distributed_table('alter_add_unique', 'x'); + +ALTER TABLE alter_add_unique ADD CONSTRAINT unique_constraint_test UNIQUE USING INDEX alter_unique_idx; +ALTER TABLE alter_add_unique DROP CONSTRAINT unique_constraint_test; + SET search_path TO 'public'; DROP SCHEMA sc1 CASCADE; diff --git a/src/test/regress/sql/multi_explain.sql b/src/test/regress/sql/multi_explain.sql index 39905e109..505eda13e 100644 --- a/src/test/regress/sql/multi_explain.sql +++ b/src/test/regress/sql/multi_explain.sql @@ -24,6 +24,17 @@ BEGIN END; $BODY$ LANGUAGE plpgsql; +CREATE FUNCTION explain_analyze_json(query text) +RETURNS jsonb +AS $BODY$ +DECLARE + result jsonb; +BEGIN + EXECUTE format('EXPLAIN (ANALYZE TRUE, FORMAT JSON) %s', query) INTO result; + RETURN result; +END; +$BODY$ LANGUAGE plpgsql; + -- Function that parses explain output as XML CREATE FUNCTION explain_xml(query text) RETURNS xml @@ -36,6 +47,19 @@ BEGIN END; $BODY$ LANGUAGE plpgsql; +-- Function that parses explain output as XML +CREATE FUNCTION explain_analyze_xml(query text) +RETURNS xml +AS $BODY$ +DECLARE + result xml; +BEGIN + EXECUTE format('EXPLAIN (ANALYZE true, FORMAT XML) %s', query) INTO result; + RETURN result; +END; +$BODY$ LANGUAGE plpgsql; + + -- VACUMM related tables to ensure test outputs are stable VACUUM ANALYZE lineitem; VACUUM ANALYZE orders; @@ -63,6 +87,13 @@ SELECT true AS valid FROM explain_json($$ SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity$$); +SELECT true AS valid FROM explain_analyze_json($$ + WITH a AS ( + SELECT l_quantity, count(*) count_quantity FROM lineitem + GROUP BY l_quantity ORDER BY count_quantity, l_quantity LIMIT 10) + SELECT count(*) FROM a +$$); + -- Test XML format EXPLAIN (COSTS FALSE, FORMAT XML) SELECT l_quantity, count(*) count_quantity FROM lineitem @@ -73,6 +104,13 @@ SELECT true AS valid FROM explain_xml($$ SELECT l_quantity, count(*) count_quantity FROM lineitem GROUP BY l_quantity ORDER BY count_quantity, l_quantity$$); +SELECT true AS valid FROM explain_analyze_xml($$ + WITH a AS ( + SELECT l_quantity, count(*) count_quantity FROM lineitem + GROUP BY l_quantity ORDER BY count_quantity, l_quantity LIMIT 10) + SELECT count(*) FROM a +$$); + -- Test YAML format EXPLAIN (COSTS FALSE, FORMAT YAML) SELECT l_quantity, count(*) count_quantity FROM lineitem diff --git a/src/test/regress/sql/multi_prepare_sql.sql b/src/test/regress/sql/multi_prepare_sql.sql index 37a5f0690..de176264f 100644 --- a/src/test/regress/sql/multi_prepare_sql.sql +++ b/src/test/regress/sql/multi_prepare_sql.sql @@ -547,22 +547,12 @@ DROP TABLE text_partition_column_table; -- Domain type columns can give issues CREATE DOMAIN test_key AS text CHECK(VALUE ~ '^test-\d$'); --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. -SELECT run_command_on_workers($$ - CREATE DOMAIN test_key AS text CHECK(VALUE ~ '^test-\d$') -$$); - CREATE TABLE domain_partition_column_table ( key test_key NOT NULL, value int ); --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; SELECT create_distributed_table('domain_partition_column_table', 'key'); -RESET citus.enable_metadata_sync; PREPARE prepared_coercion_to_domain_insert(text) AS INSERT INTO domain_partition_column_table VALUES ($1, 1); diff --git a/src/test/regress/sql/prepared_statements_4.sql b/src/test/regress/sql/prepared_statements_4.sql index 4aa79387c..a8f124568 100644 --- a/src/test/regress/sql/prepared_statements_4.sql +++ b/src/test/regress/sql/prepared_statements_4.sql @@ -14,21 +14,16 @@ EXECUTE prepared_relabel_insert('test'); SELECT key, value FROM text_partition_column_table ORDER BY key; +PREPARE prepared_coercion_to_domain_insert(text) AS + INSERT INTO domain_partition_column_table VALUES ($1, 1); - --- TODO: Uncomment tests below once domains are supported --- PREPARE prepared_coercion_to_domain_insert(text) AS --- INSERT INTO domain_partition_column_table VALUES ($1, 1); - --- EXECUTE prepared_coercion_to_domain_insert('test-1'); --- EXECUTE prepared_coercion_to_domain_insert('test-2'); --- EXECUTE prepared_coercion_to_domain_insert('test-3'); --- EXECUTE prepared_coercion_to_domain_insert('test-4'); --- EXECUTE prepared_coercion_to_domain_insert('test-5'); --- EXECUTE prepared_coercion_to_domain_insert('test-6'); --- EXECUTE prepared_coercion_to_domain_insert('test-7'); - - +EXECUTE prepared_coercion_to_domain_insert('test-1'); +EXECUTE prepared_coercion_to_domain_insert('test-2'); +EXECUTE prepared_coercion_to_domain_insert('test-3'); +EXECUTE prepared_coercion_to_domain_insert('test-4'); +EXECUTE prepared_coercion_to_domain_insert('test-5'); +EXECUTE prepared_coercion_to_domain_insert('test-6'); +EXECUTE prepared_coercion_to_domain_insert('test-7'); PREPARE FOO AS INSERT INTO http_request ( site_id, ingest_time, url, request_country, diff --git a/src/test/regress/sql/prepared_statements_create_load.sql b/src/test/regress/sql/prepared_statements_create_load.sql index af7baa026..6afb1a12f 100644 --- a/src/test/regress/sql/prepared_statements_create_load.sql +++ b/src/test/regress/sql/prepared_statements_create_load.sql @@ -54,24 +54,12 @@ SELECT create_distributed_table('text_partition_column_table', 'key'); -- and we use offset to prevent output diverging CREATE DOMAIN test_key AS text CHECK(VALUE ~ '^test-\d$'); - --- TODO: Once domains are supported, remove enable_metadata_sync off/on change --- on dependent table distribution below. Also uncomment related tests on --- prepared_statements_4 test file. -SELECT run_command_on_workers($$ - CREATE DOMAIN "prepared statements".test_key AS text CHECK(VALUE ~ '^test-\d$') -$$) OFFSET 10000; - --- Disable metadata sync since citus doesn't support distributing --- domains for now. -SET citus.enable_metadata_sync TO OFF; CREATE TABLE domain_partition_column_table ( key test_key NOT NULL, value int ); SELECT create_distributed_table('domain_partition_column_table', 'key'); -RESET citus.enable_metadata_sync; -- verify we re-evaluate volatile functions every time CREATE TABLE http_request ( diff --git a/src/test/regress/sql/shard_move_deferred_delete.sql b/src/test/regress/sql/shard_move_deferred_delete.sql index 9dc4f82e3..0b5d1a872 100644 --- a/src/test/regress/sql/shard_move_deferred_delete.sql +++ b/src/test/regress/sql/shard_move_deferred_delete.sql @@ -167,17 +167,30 @@ SET search_path TO shard_move_deferred_delete; -- When there would not be enough free space left after the move, the move should fail SELECT master_move_shard_placement(20000001, 'localhost', :worker_2_port, 'localhost', :worker_1_port); --- Restore the original function -SELECT run_command_on_workers($cmd$ - CREATE OR REPLACE FUNCTION pg_catalog.citus_local_disk_space_stats( - OUT available_disk_size bigint, - OUT total_disk_size bigint) - RETURNS record - LANGUAGE C STRICT - AS 'citus', $$citus_local_disk_space_stats$$; - COMMENT ON FUNCTION pg_catalog.citus_local_disk_space_stats() - IS 'returns statistics on available disk space on the local node'; -$cmd$); +-- Restore the original function on workers +\c - - - :worker_1_port +SET citus.enable_metadata_sync TO OFF; +CREATE OR REPLACE FUNCTION pg_catalog.citus_local_disk_space_stats( +OUT available_disk_size bigint, +OUT total_disk_size bigint) +RETURNS record +LANGUAGE C STRICT +AS 'citus', $$citus_local_disk_space_stats$$; +COMMENT ON FUNCTION pg_catalog.citus_local_disk_space_stats() +IS 'returns statistics on available disk space on the local node'; +\c - - - :worker_2_port +SET citus.enable_metadata_sync TO OFF; + +CREATE OR REPLACE FUNCTION pg_catalog.citus_local_disk_space_stats( +OUT available_disk_size bigint, +OUT total_disk_size bigint) +RETURNS record +LANGUAGE C STRICT +AS 'citus', $$citus_local_disk_space_stats$$; +COMMENT ON FUNCTION pg_catalog.citus_local_disk_space_stats() +IS 'returns statistics on available disk space on the local node'; + +\c - - - :master_port DROP SCHEMA shard_move_deferred_delete CASCADE; diff --git a/src/test/regress/sql/tableam.sql b/src/test/regress/sql/tableam.sql index 3c7cb69f0..1ddc7a90c 100644 --- a/src/test/regress/sql/tableam.sql +++ b/src/test/regress/sql/tableam.sql @@ -15,6 +15,7 @@ create schema test_tableam; set search_path to test_tableam; SELECT public.run_command_on_coordinator_and_workers($Q$ + SET citus.enable_ddl_propagation TO off; CREATE FUNCTION fake_am_handler(internal) RETURNS table_am_handler AS 'citus'