added citus_depended_objects udf and HideCitusDependentObjects GUC to hide citus depended objects from pg meta queries (#6055)

use RecurseObjectDependencies api to find if an object is citus depended

make vanilla tests runnable to see if citus_depended function is working correctly
pull/6075/head
aykut-bozkurt 2022-07-25 16:43:34 +03:00 committed by GitHub
parent e2a9495334
commit 67ac3da2b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1146 additions and 17 deletions

View File

@ -34,7 +34,9 @@
#include "catalog/pg_rewrite_d.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_type.h"
#include "commands/extension.h"
#include "common/hashfn.h"
#include "distributed/citus_depended_object.h"
#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/listutils.h"
@ -168,11 +170,18 @@ static bool FollowNewSupportedDependencies(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static bool FollowAllDependencies(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static bool FollowExtAndInternalDependencies(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static void ApplyAddToDependencyList(ObjectAddressCollector *collector,
DependencyDefinition *definition);
static void ApplyAddCitusDependedObjectsToDependencyList(
ObjectAddressCollector *collector,
DependencyDefinition *definition);
static List * GetViewRuleReferenceDependencyList(Oid relationId);
static List * ExpandCitusSupportedTypes(ObjectAddressCollector *collector,
ObjectAddress target);
static List * ExpandForPgVanilla(ObjectAddressCollector *collector,
ObjectAddress target);
static List * GetDependentRoleIdsFDW(Oid FDWOid);
static List * ExpandRolesToGroups(Oid roleid);
static ViewDependencyNode * BuildViewDependencyGraph(Oid relationId, HTAB *nodeMap);
@ -280,6 +289,26 @@ GetAllDependenciesForObject(const ObjectAddress *target)
}
/*
* GetAllCitusDependedDependenciesForObject returns all the dependencies
* which are owned by citus extension for the target.
*/
List *
GetAllCitusDependedDependenciesForObject(const ObjectAddress *target)
{
ObjectAddressCollector collector = { 0 };
InitObjectAddressCollector(&collector);
RecurseObjectDependencies(*target,
&ExpandForPgVanilla,
&FollowExtAndInternalDependencies,
&ApplyAddCitusDependedObjectsToDependencyList,
&collector);
return collector.dependencyList;
}
/*
* OrderObjectAddressListInDependencyOrder given a list of ObjectAddresses return a new
* list of the same ObjectAddresses ordered on dependency order where dependencies
@ -1121,6 +1150,37 @@ IsAnyObjectAddressOwnedByExtension(const List *targets,
}
/*
* IsObjectAddressOwnedByCitus returns true if the given object address
* is owned by the citus or citus_columnar extensions.
*/
bool
IsObjectAddressOwnedByCitus(const ObjectAddress *objectAddress)
{
Oid citusId = get_extension_oid("citus", true);
Oid citusColumnarId = get_extension_oid("citus_columnar", true);
/* return false because we could not find any citus extension */
if (!OidIsValid(citusId) && !OidIsValid(citusColumnarId))
{
return false;
}
ObjectAddress extObjectAddress = InvalidObjectAddress;
bool ownedByExt = IsObjectAddressOwnedByExtension(objectAddress,
&extObjectAddress);
if (!ownedByExt)
{
return false;
}
bool ownedByCitus = extObjectAddress.objectId == citusId;
bool ownedByCitusColumnar = extObjectAddress.objectId == citusColumnarId;
return ownedByCitus || ownedByCitusColumnar;
}
/*
* FollowNewSupportedDependencies applies filters on pg_depend entries to follow all
* objects which should be distributed before the root object can safely be created.
@ -1302,6 +1362,39 @@ FollowAllDependencies(ObjectAddressCollector *collector,
}
/*
* FollowExtAndInternalDependencies applies filters on pg_depend entries to follow
* the dependency tree of objects in depth first order. We will visit all objects
* irrespective of it is supported by Citus or not and it is internal or not.
*/
static bool
FollowExtAndInternalDependencies(ObjectAddressCollector *collector,
DependencyDefinition *definition)
{
ObjectAddress address = DependencyDefinitionObjectAddress(definition);
/*
* If the object is already in our dependency list we do not have to follow any
* further
*/
if (IsObjectAddressCollected(address, collector))
{
return false;
}
if (CitusExtensionObject(&address))
{
/*
* We do not need to follow citus extension because the purpose
* of our walk is to find if an object is owned by citus.
*/
return false;
}
return true;
}
/*
* ApplyAddToDependencyList is an apply function for RecurseObjectDependencies that will
* collect all the ObjectAddresses for pg_depend entries to the context, except it is
@ -1332,6 +1425,30 @@ ApplyAddToDependencyList(ObjectAddressCollector *collector,
}
/*
* ApplyAddCitusDependedObjectsToDependencyList is an apply function for
* RecurseObjectDependencies that will collect all the ObjectAddresses for
* pg_depend entries to the context if it is citus extension owned one.
*
* The context here is assumed to be a (ObjectAddressCollector *) to the location where
* all ObjectAddresses will be collected.
*/
static void
ApplyAddCitusDependedObjectsToDependencyList(ObjectAddressCollector *collector,
DependencyDefinition *definition)
{
ObjectAddress address = DependencyDefinitionObjectAddress(definition);
/*
* We only collect the object if it is owned by citus extension.
*/
if (IsObjectAddressOwnedByCitus(&address))
{
CollectObjectAddress(collector, &address);
}
}
/*
* ExpandCitusSupportedTypes base on supported types by citus we might want to expand
* the list of objects to visit in pg_depend.
@ -1515,6 +1632,39 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
}
/*
* ExpandForPgVanilla only expands only comosite types because other types
* will find their dependencies in pg_depend. The method should only be called by
* is_citus_depended_object udf.
*/
static List *
ExpandForPgVanilla(ObjectAddressCollector *collector,
ObjectAddress target)
{
/* should only be called if GUC is enabled */
Assert(HideCitusDependentObjects == true);
List *result = NIL;
if (target.classId == TypeRelationId && get_typtype(target.objectId) ==
TYPTYPE_COMPOSITE)
{
/*
* 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.
*/
Oid typeRelationId = get_typ_typrelid(target.objectId);
DependencyDefinition *dependency =
CreateObjectAddressDependencyDef(RelationRelationId,
typeRelationId);
result = lappend(result, dependency);
}
return result;
}
/*
* GetDependentRoleIdsFDW returns a list of role oids that has privileges on the
* FDW with the given object id.

View File

@ -35,6 +35,7 @@
#include "commands/extension.h"
#include "commands/trigger.h"
#include "distributed/backend_data.h"
#include "distributed/citus_depended_object.h"
#include "distributed/colocation_utils.h"
#include "distributed/connection_management.h"
#include "distributed/citus_ruleutils.h"
@ -182,6 +183,7 @@ typedef struct MetadataCacheData
Oid relationIsAKnownShardFuncId;
Oid jsonbExtractPathFuncId;
Oid jsonbExtractPathTextFuncId;
Oid CitusDependentObjectFuncId;
bool databaseNameValid;
char databaseName[NAMEDATALEN];
} MetadataCacheData;
@ -2896,6 +2898,30 @@ JsonbExtractPathTextFuncId(void)
}
/*
* CitusDependentObjectFuncId returns oid of the is_citus_depended_object function.
*/
Oid
CitusDependentObjectFuncId(void)
{
if (!HideCitusDependentObjects)
{
ereport(ERROR, (errmsg(
"is_citus_depended_object can only be used while running the regression tests")));
}
if (MetadataCache.CitusDependentObjectFuncId == InvalidOid)
{
const int argCount = 2;
MetadataCache.CitusDependentObjectFuncId =
FunctionOid("pg_catalog", "is_citus_depended_object", argCount);
}
return MetadataCache.CitusDependentObjectFuncId;
}
/*
* CurrentDatabaseName gets the name of the current database and caches
* the result.

View File

@ -17,9 +17,11 @@
#include <limits.h>
#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "distributed/citus_depended_object.h"
#include "distributed/citus_nodefuncs.h"
#include "distributed/citus_nodes.h"
#include "distributed/citus_ruleutils.h"
@ -204,6 +206,13 @@ distributed_planner(Query *parse,
*/
HideShardsFromSomeApplications(parse);
/*
* If GUC is set, we prevent queries, which contain pg meta relations, from
* showing any citus dependent object. The flag is expected to be set only before
* postgres vanilla tests.
*/
HideCitusDependentObjectsOnQueriesOfPgMetaTables((Node *) parse, NULL);
/* create a restriction context and put it at the end if context list */
planContext.plannerRestrictionContext = CreateAndPushPlannerRestrictionContext();
@ -345,6 +354,17 @@ ListContainsDistributedTableRTE(List *rangeTableList,
continue;
}
if (HideCitusDependentObjects && IsolationIsSerializable() && IsPgLocksTable(
rangeTableEntry))
{
/*
* Postgres tidscan.sql test fails if we do not filter pg_locks table because
* test results, which show taken locks in serializable isolation mode,
* fails by showing extra lock taken by IsCitusTable below.
*/
continue;
}
if (IsCitusTable(rangeTableEntry->relid))
{
if (maybeHasForeignDistributedTable != NULL &&

View File

@ -25,6 +25,7 @@
#include "commands/explain.h"
#include "commands/tablecmds.h"
#include "optimizer/cost.h"
#include "distributed/citus_depended_object.h"
#include "distributed/citus_nodefuncs.h"
#include "distributed/connection_management.h"
#include "distributed/deparse_shard_query.h"
@ -1185,6 +1186,20 @@ CitusExplainOneQuery(Query *query, int cursorOptions, IntoClause *into,
INSTR_TIME_SET_CURRENT(planstart);
/*
* We should not hide any objects while explaining some query to not break
* postgres vanilla tests.
*
* The filter 'is_citus_depended_object' is added to explain result
* and causes some tests to fail if HideCitusDependentObjects is true.
* Therefore, we disable HideCitusDependentObjects until the current transaction
* ends.
*
* We do not use security quals because a postgres vanilla test fails
* with a change of order for its result.
*/
SetLocalHideCitusDependentObjectsDisabledWhenAlreadyEnabled();
/* plan the query */
PlannedStmt *plan = pg_plan_query_compat(query, NULL, cursorOptions, params);
INSTR_TIME_SET_CURRENT(planduration);

View File

@ -32,6 +32,7 @@
#include "common/string.h"
#include "executor/executor.h"
#include "distributed/backend_data.h"
#include "distributed/citus_depended_object.h"
#include "distributed/citus_nodefuncs.h"
#include "distributed/citus_safe_lib.h"
#include "distributed/commands.h"
@ -1272,6 +1273,18 @@ RegisterCitusConfigVariables(void)
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
DefineCustomBoolVariable(
"citus.hide_citus_dependent_objects",
gettext_noop(
"Hides some objects, which depends on citus extension, from pg meta class queries."
"It is intended to be used only before postgres vanilla tests to not break them."),
NULL,
&HideCitusDependentObjects,
false,
PGC_USERSET,
GUC_SUPERUSER_ONLY | GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
/*
* This was a GUC we added on Citus 11.0.1, and
* replaced with another name on 11.0.2 via #5920.

View File

@ -0,0 +1,151 @@
/*
* citus_depended_object.c
*
* Implements udf function related to hiding citus depended objects while executing
* postgres vanilla tests.
*
* Copyright (c) Citus Data, Inc.
*/
#include "postgres.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_am.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_class.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "distributed/citus_depended_object.h"
#include "distributed/listutils.h"
#include "distributed/metadata_cache.h"
#include "distributed/metadata/dependency.h"
#include "distributed/metadata/distobject.h"
static bool IsCitusDependentObject(ObjectAddress objectAddress);
PG_FUNCTION_INFO_V1(is_citus_depended_object);
/*
* is_citus_depended_object a wrapper around IsCitusDependentObject, so
* see the details there.
*
* The first parameter expects an oid for
* a pg meta class, and the second parameter expects an oid for
* the object which is found in the pg meta class.
*/
Datum
is_citus_depended_object(PG_FUNCTION_ARGS)
{
CheckCitusVersion(ERROR);
if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
{
/* Because we want to return false for null arguments, we donot use strict keyword while creating that function. */
PG_RETURN_BOOL(false);
}
Oid metaTableId = PG_GETARG_OID(0);
Oid objectId = PG_GETARG_OID(1);
if (!OidIsValid(metaTableId) || !OidIsValid(objectId))
{
/* we cannot continue without valid meta table or object oid */
PG_RETURN_BOOL(false);
}
bool dependsOnCitus = false;
ObjectAddress objectAdress = { metaTableId, objectId, 0 };
switch (metaTableId)
{
case ProcedureRelationId:
case AccessMethodRelationId:
case EventTriggerRelationId:
case TriggerRelationId:
case TSConfigRelationId:
case TSTemplateRelationId:
case TSDictionaryRelationId:
case LanguageRelationId:
case RewriteRelationId:
case AttrDefaultRelationId:
case NamespaceRelationId:
case ConstraintRelationId:
case TypeRelationId:
case RelationRelationId:
{
/* meta classes that access their own oid */
dependsOnCitus = IsCitusDependentObject(objectAdress);
break;
}
case EnumRelationId:
{
/*
* we do not directly access the oid in pg_enum,
* because it does not exist in pg_depend, but its type does
*/
objectAdress.classId = TypeRelationId;
dependsOnCitus = IsCitusDependentObject(objectAdress);
break;
}
case IndexRelationId:
case AttributeRelationId:
case SequenceRelationId:
case StatisticRelationId:
{
/* meta classes that access their relation's oid */
objectAdress.classId = RelationRelationId;
dependsOnCitus = IsCitusDependentObject(objectAdress);
break;
}
case AggregateRelationId:
{
/* We access procedure oid for aggregates. */
objectAdress.classId = ProcedureRelationId;
dependsOnCitus = IsCitusDependentObject(objectAdress);
break;
}
default:
{
break;
}
}
PG_RETURN_BOOL(dependsOnCitus);
}
/*
* IsCitusDependentObject returns true if the given object depends on the citus extension.
*/
static bool
IsCitusDependentObject(ObjectAddress objectAddress)
{
if (IsObjectAddressOwnedByCitus(&objectAddress))
{
/* object itself is owned by citus */
return true;
}
/* check if object's any dependency is owned by citus. */
List *citusDependencies = GetAllCitusDependedDependenciesForObject(&objectAddress);
return list_length(citusDependencies) > 0;
}

View File

@ -0,0 +1,287 @@
/*
* citus_depended_object.c
*
* Implements exposed functions related to hiding citus depended objects.
*
* Copyright (c) Citus Data, Inc.
*/
#include "postgres.h"
#include "miscadmin.h"
#include "catalog/namespace.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_am.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_class.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "distributed/citus_depended_object.h"
#include "distributed/metadata_cache.h"
#include "distributed/listutils.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/parsenodes.h"
#include "utils/lsyscache.h"
/*
* GUC hides any objects, which depends on citus extension, from pg meta class queries,
* it is intended to be used in vanilla tests to not break postgres test logs
*/
bool HideCitusDependentObjects = false;
static Node * CreateCitusDependentObjectExpr(int pgMetaTableVarno, int pgMetaTableOid);
static List * GetCitusDependedObjectArgs(int pgMetaTableVarno, int pgMetaTableOid);
/*
* IsPgLocksTable returns true if RTE is pg_locks table.
*/
bool
IsPgLocksTable(RangeTblEntry *rte)
{
Oid pgLocksId = get_relname_relid("pg_locks", get_namespace_oid("pg_catalog", false));
return rte->relid == pgLocksId;
}
/*
* SetLocalHideCitusDependentObjectsDisabledWhenAlreadyEnabled disables the GUC HideCitusDependentObjects
* if only it is enabled for local transaction.
*/
void
SetLocalHideCitusDependentObjectsDisabledWhenAlreadyEnabled(void)
{
if (!HideCitusDependentObjects)
{
return;
}
set_config_option("citus.hide_citus_dependent_objects", "false",
(superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION,
GUC_ACTION_LOCAL, true, 0, false);
}
/*
* HideCitusDependentObjectsOnQueriesOfPgMetaTables adds a NOT is_citus_depended_object(oid, oid) expr
* to the quals of meta class RTEs that we are interested in.
*/
bool
HideCitusDependentObjectsOnQueriesOfPgMetaTables(Node *node, void *context)
{
if (!CitusHasBeenLoaded() || !HideCitusDependentObjects || node == NULL)
{
return false;
}
if (IsA(node, Query))
{
Query *query = (Query *) node;
MemoryContext queryContext = GetMemoryChunkContext(query);
/*
* We process the whole rtable rather than visiting individual RangeTblEntry's
* in the walker, since we need to know the varno to generate the right
* filter.
*/
int varno = 0;
RangeTblEntry *rangeTableEntry = NULL;
foreach_ptr(rangeTableEntry, query->rtable)
{
varno++;
if (rangeTableEntry->rtekind == RTE_RELATION)
{
/* make sure the expression is in the right memory context */
MemoryContext originalContext = MemoryContextSwitchTo(queryContext);
Oid metaTableOid = InvalidOid;
/*
* add NOT is_citus_depended_object(oid, oid) to the quals
* of the RTE if it is a pg meta table that we are interested in.
*/
switch (rangeTableEntry->relid)
{
/* pg_class */
case RelationRelationId:
/* pg_proc */
case ProcedureRelationId:
/* pg_am */
case AccessMethodRelationId:
/* pg_type */
case TypeRelationId:
/* pg_enum */
case EnumRelationId:
/* pg_event_trigger */
case EventTriggerRelationId:
/* pg_trigger */
case TriggerRelationId:
/* pg_rewrite */
case RewriteRelationId:
/* pg_attrdef */
case AttrDefaultRelationId:
/* pg_constraint */
case ConstraintRelationId:
/* pg_ts_config */
case TSConfigRelationId:
/* pg_ts_template */
case TSTemplateRelationId:
/* pg_ts_dict */
case TSDictionaryRelationId:
/* pg_language */
case LanguageRelationId:
/* pg_namespace */
case NamespaceRelationId:
/* pg_sequence */
case SequenceRelationId:
/* pg_statistic */
case StatisticRelationId:
/* pg_attribute */
case AttributeRelationId:
/* pg_index */
case IndexRelationId:
/* pg_aggregate */
case AggregateRelationId:
{
metaTableOid = rangeTableEntry->relid;
break;
}
default:
{
metaTableOid = InvalidOid;
break;
}
}
if (OidIsValid(metaTableOid))
{
/*
* We found a valid pg meta class in query,
* so we assert below conditions.
*/
Assert(query->jointree != NULL);
Assert(query->jointree->fromlist != NULL);
Node *citusDependentObjExpr =
CreateCitusDependentObjectExpr(varno, metaTableOid);
/*
* We do not use security quals because a postgres vanilla test fails
* with a change of order for its result.
*/
query->jointree->quals = make_and_qual(
query->jointree->quals, citusDependentObjExpr);
}
MemoryContextSwitchTo(originalContext);
}
}
return query_tree_walker((Query *) node,
HideCitusDependentObjectsOnQueriesOfPgMetaTables,
context, 0);
}
return expression_tree_walker(node, HideCitusDependentObjectsOnQueriesOfPgMetaTables,
context);
}
/*
* CreateCitusDependentObjectExpr constructs an expression of the form:
* NOT pg_catalog.is_citus_depended_object(oid, oid)
*/
static Node *
CreateCitusDependentObjectExpr(int pgMetaTableVarno, int pgMetaTableOid)
{
/* build the call to read_intermediate_result */
FuncExpr *funcExpr = makeNode(FuncExpr);
funcExpr->funcid = CitusDependentObjectFuncId();
funcExpr->funcretset = false;
funcExpr->funcvariadic = false;
funcExpr->funcformat = 0;
funcExpr->funccollid = 0;
funcExpr->inputcollid = 0;
funcExpr->location = -1;
funcExpr->args = GetCitusDependedObjectArgs(pgMetaTableVarno, pgMetaTableOid);
BoolExpr *notExpr = makeNode(BoolExpr);
notExpr->boolop = NOT_EXPR;
notExpr->args = list_make1(funcExpr);
notExpr->location = -1;
return (Node *) notExpr;
}
/*
* GetCitusDependedObjectArgs returns func arguments for pg_catalog.is_citus_depended_object
*/
static List *
GetCitusDependedObjectArgs(int pgMetaTableVarno, int pgMetaTableOid)
{
/*
* set attribute number for the oid, which we are insterest in, inside pg meta tables.
* We are accessing the 1. col(their own oid or their relation's oid) to get the related
* object's oid for all of the pg meta tables except pg_enum and pg_index. For pg_enum,
* class, we access its 2. col(its type's oid) to see if its type depends on citus,
* so it does. For pg_index, we access its 2. col (its relation's oid) to see if its relation
* depends on citus, so it does.
*/
AttrNumber oidAttNum = 1;
if (pgMetaTableOid == EnumRelationId || pgMetaTableOid == IndexRelationId)
{
oidAttNum = 2;
}
/* create const for meta table oid */
Const *metaTableOidConst = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid),
ObjectIdGetDatum(pgMetaTableOid),
false, true);
/*
* create a var for the oid that we are interested in,
* col type should be regproc for pg_aggregate table; else oid
*/
Oid varType = (pgMetaTableOid == AggregateRelationId) ? REGPROCOID : OIDOID;
Var *oidVar = makeVar(pgMetaTableVarno, oidAttNum,
varType, -1, InvalidOid, 0);
return list_make2((Node *) metaTableOidConst, (Node *) oidVar);
}

View File

@ -0,0 +1,24 @@
/*-------------------------------------------------------------------------
*
* citus_depended_object.h
* Exposes functions related to hiding citus depended objects while executing
* postgres vanilla tests.
*
* Copyright (c) CitusDependent Data, Inc.
*
*-------------------------------------------------------------------------
*/
#ifndef CITUS_DEPENDED_OBJECT_H
#define CITUS_DEPENDED_OBJECT_H
#include "nodes/nodes.h"
#include "nodes/parsenodes.h"
extern bool HideCitusDependentObjects;
extern void SetLocalHideCitusDependentObjectsDisabledWhenAlreadyEnabled(void);
extern bool HideCitusDependentObjectsOnQueriesOfPgMetaTables(Node *node, void *context);
extern bool IsPgLocksTable(RangeTblEntry *rte);
#endif /* CITUS_DEPENDED_OBJECT_H */

View File

@ -26,6 +26,7 @@ extern List * GetAllDependenciesForObject(const ObjectAddress *target);
extern bool ErrorOrWarnIfAnyObjectHasUnsupportedDependency(List *objectAddresses);
extern DeferredErrorMessage * DeferErrorIfAnyObjectHasUnsupportedDependency(const List *
objectAddresses);
extern List * GetAllCitusDependedDependenciesForObject(const ObjectAddress *target);
extern List * OrderObjectAddressListInDependencyOrder(List *objectAddressList);
extern bool SupportedDependencyByCitus(const ObjectAddress *address);
extern List * GetPgDependTuplesForDependingObjects(Oid targetObjectClassId,

View File

@ -29,6 +29,7 @@ extern void UnmarkObjectDistributed(const ObjectAddress *address);
extern bool IsTableOwnedByExtension(Oid relationId);
extern bool IsAnyObjectAddressOwnedByExtension(const List *targets,
ObjectAddress *extensionAddress);
extern bool IsObjectAddressOwnedByCitus(const ObjectAddress *objectAddress);
extern ObjectAddress PgGetObjectAddress(char *ttype, ArrayType *namearr,
ArrayType *argsarr);
extern List * GetDistributedObjectAddressList(void);

View File

@ -263,6 +263,7 @@ extern Oid CitusTableVisibleFuncId(void);
extern Oid RelationIsAKnownShardFuncId(void);
extern Oid JsonbExtractPathFuncId(void);
extern Oid JsonbExtractPathTextFuncId(void);
extern Oid CitusDependentObjectFuncId(void);
/* enum oids */
extern Oid PrimaryNodeRoleId(void);

View File

@ -28,3 +28,24 @@
# core dumps
core
# postgres vanilla test's outputs
constraints.sql
copy.sql
create_function_0.sql
create_function_1.sql
create_function_2.sql
largeobject.sql
misc.sql
security_label.sql
tablespace.sql
constraints.out
copy.out
create_function_0.out
create_function_1.out
create_function_2.out
largeobject.out
largeobject_1.out
misc.out
security_label.out
tablespace.out

View File

@ -188,13 +188,8 @@ check-isolation-base: all $(isolation_test_files)
$(pg_regress_multi_check) --load-extension=citus --isolationtester \
-- $(MULTI_REGRESS_OPTS) --inputdir=$(citus_abs_srcdir)/build --schedule=$(citus_abs_srcdir)/base_isolation_schedule $(EXTRA_TESTS)
check-vanilla: all
# it is possible that sometimes vanilla tests will fail, which is related to postgres.
# So we try it once more if it fails to prevent some failures in our CI.
${MAKE} check-vanilla-internal || ${MAKE} check-vanilla-internal
check-vanilla-internal:
$(pg_regress_multi_check) --load-extension=citus --vanillatest
check-vanilla:
$(pg_regress_multi_check) --vanillatest --vanilla-dev
check-multi-mx: all
$(pg_regress_multi_check) --load-extension=citus \

View File

@ -0,0 +1,195 @@
-- create the udf is_citus_depended_object that is needed for the tests
CREATE OR REPLACE FUNCTION
pg_catalog.is_citus_depended_object(oid,oid)
RETURNS bool
LANGUAGE C
AS 'citus', $$is_citus_depended_object$$;
-- execute tests in a separate namespace
CREATE SCHEMA citus_dependend_object;
SET search_path TO citus_dependend_object;
-- PG_CLASS VISIBILITY
-- check if we correctly determine whether a relation is citus dependent or not.
CREATE TABLE no_hide_pg_class(relname text);
CREATE TABLE hide_pg_class(relname text);
-- create a relation that depends on noderole type which is a citus object
CREATE TABLE citus_depended_class(nrole noderole);
-- create a relation that depends on columnar access method which is a citus object
CREATE TABLE citus_depended_class2(id int);
SELECT alter_table_set_access_method('citus_depended_class2', 'columnar');
NOTICE: creating a new table for citus_dependend_object.citus_depended_class2
NOTICE: moving the data of citus_dependend_object.citus_depended_class2
NOTICE: dropping the old citus_dependend_object.citus_depended_class2
NOTICE: renaming the new table to citus_dependend_object.citus_depended_class2
alter_table_set_access_method
---------------------------------------------------------------------
(1 row)
-- create a relation that does not depend on citus
CREATE TABLE citus_independed_class(id int);
-- store all relations
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_class SELECT relname FROM pg_class;
-- store all relations except citus relations
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_class SELECT relname FROM pg_class;
-- prove that some relations are hidden or not
SELECT relname,
CASE
WHEN relname IN
(
SELECT relname FROM no_hide_pg_class
EXCEPT
SELECT relname FROM hide_pg_class
) THEN true
ELSE false
END AS is_hidden
FROM (VALUES ('pg_dist_shard'), ('pg_dist_placement'), ('pg_type'), ('pg_proc'),
('citus_depended_class'), ('citus_depended_class2'), ('citus_independed_class')) rels(relname);
relname | is_hidden
---------------------------------------------------------------------
pg_dist_shard | t
pg_dist_placement | t
pg_type | f
pg_proc | f
citus_depended_class | t
citus_depended_class2 | t
citus_independed_class | f
(7 rows)
-- PG_TYPE VISIBILITY
-- check if we correctly determine whether a type is citus dependent or not.
CREATE TABLE no_hide_pg_type(typname text);
CREATE TABLE hide_pg_type(typname text);
-- create a type that depends on noderole type which is a citus object
CREATE TYPE citus_depended_type AS (nrole noderole);
-- create a relation that does not depend on citus
CREATE TYPE citus_independed_type AS (id int);
-- store all types
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_type SELECT typname FROM pg_type;
-- store all types except citus types
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_type SELECT typname FROM pg_type;
-- prove that some types are hidden or not
SELECT typname,
CASE
WHEN typname IN
(
SELECT typname FROM no_hide_pg_type
EXCEPT
SELECT typname FROM hide_pg_type
) THEN true
ELSE false
END AS is_hidden
FROM (VALUES ('noderole'), ('_noderole'), ('int'), ('_int'),
('citus_depended_type'), ('citus_independed_type')) types(typname);
typname | is_hidden
---------------------------------------------------------------------
noderole | t
_noderole | t
int | f
_int | f
citus_depended_type | t
citus_independed_type | f
(6 rows)
-- PG_AM VISIBILITY
-- check if we correctly determine whether an access method is citus dependent or not.
CREATE TABLE no_hide_pg_am(amname text);
CREATE TABLE hide_pg_am(amname text);
-- store all access methods
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_am SELECT amname FROM pg_am;
-- store all access methods except citus access methods
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_am SELECT amname FROM pg_am;
-- show all hidden access methods
SELECT amname AS hidden_am FROM no_hide_pg_am
EXCEPT
SELECT amname AS hidden_am FROM hide_pg_am
ORDER BY 1;
hidden_am
---------------------------------------------------------------------
columnar
(1 row)
-- show all unhidden access methods
SELECT amname AS unhidden_am FROM no_hide_pg_am
EXCEPT
(
SELECT amname FROM no_hide_pg_am
EXCEPT
SELECT amname FROM hide_pg_am
)
ORDER BY 1;
unhidden_am
---------------------------------------------------------------------
brin
btree
gin
gist
hash
heap
spgist
(7 rows)
-- PG_PROC VISIBILITY
-- check if we correctly determine whether a procedure is citus dependent or not.
CREATE TABLE no_hide_pg_proc(proname text);
CREATE TABLE hide_pg_proc(proname text);
-- create a procedure that depends on noderole type which is a citus object
CREATE OR REPLACE PROCEDURE citus_depended_proc(nrole noderole)
LANGUAGE SQL
AS $$
$$;
-- create a procedure that does not depend on citus
CREATE OR REPLACE PROCEDURE citus_independed_proc(id int)
LANGUAGE SQL
AS $$
$$;
-- store all access procedures
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_proc SELECT proname FROM pg_proc;
-- store all access procedures except citus procedures
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_proc SELECT proname FROM pg_proc;
-- prove that some procedures are hidden or not
SELECT proname,
CASE
WHEN proname IN
(
SELECT proname FROM no_hide_pg_proc
EXCEPT
SELECT proname FROM hide_pg_proc
) THEN true
ELSE false
END AS is_hidden
FROM (VALUES ('master_add_node'), ('format'),
('citus_depended_proc'), ('citus_independed_proc')) procs(proname);
proname | is_hidden
---------------------------------------------------------------------
master_add_node | t
format | f
citus_depended_proc | t
citus_independed_proc | f
(4 rows)
-- drop the namespace with all its objects
DROP SCHEMA citus_dependend_object CASCADE;
NOTICE: drop cascades to 15 other objects
DETAIL: drop cascades to table no_hide_pg_class
drop cascades to table hide_pg_class
drop cascades to table citus_depended_class
drop cascades to table citus_depended_class2
drop cascades to table citus_independed_class
drop cascades to table no_hide_pg_type
drop cascades to table hide_pg_type
drop cascades to type citus_depended_type
drop cascades to type citus_independed_type
drop cascades to table no_hide_pg_am
drop cascades to table hide_pg_am
drop cascades to table no_hide_pg_proc
drop cascades to table hide_pg_proc
drop cascades to function citus_depended_proc(noderole)
drop cascades to function citus_independed_proc(integer)

View File

@ -46,7 +46,7 @@ test: multi_read_from_secondaries
# ----------
# multi_citus_tools tests utility functions written for citus tools
# ----------
test: multi_citus_tools
test: multi_citus_tools citus_depended_object
# ----------
# multi_replicate_reference_table tests replicating reference tables to new nodes after we add new nodes

View File

@ -60,6 +60,7 @@ my $MASTER_FOLLOWERDIR = 'master-follower';
# Option parsing
my $isolationtester = 0;
my $vanillatest = 0;
my $vanillaDev = 0;
my $followercluster = 0;
my $bindir = "";
my $libdir = undef;
@ -96,6 +97,7 @@ if ($Config{osname} eq "MSWin32")
GetOptions(
'isolationtester' => \$isolationtester,
'vanillatest' => \$vanillatest,
'vanilla-dev' => \$vanillaDev,
'follower-cluster' => \$followercluster,
'bindir=s' => \$bindir,
'libdir=s' => \$libdir,
@ -486,6 +488,20 @@ push(@pgOptions, "citus.show_shards_for_app_name_prefixes='pg_regress'");
# we disable slow start by default to encourage parallelism within tests
push(@pgOptions, "citus.executor_slow_start_interval=0ms");
###
# we set some GUCs to not break postgres vanilla tests
#
# NOTE: we do not want to set the option right now because
# vanilla tests currently fail. We will remove the vanillaDev flag
# after fully supported pg vanilla tests with citus
# extension created.
###
if(!$vanillaDev && $vanillatest)
{
# we enable hiding the citus dependent objects from pg meta class queries to not break postgres vanilla test behaviour
push(@pgOptions, "citus.hide_citus_dependent_objects=true");
}
if ($useMitmproxy)
{
# make tests reproducible by never trying to negotiate ssl
@ -994,26 +1010,88 @@ my $startTime = time();
my $exitcode = 0;
sub PrepForVanillaTest
{
###
# We want to add is_citus_depended_object function to the default db.
# But without use-existing flag, pg_regress drops if exist and creates
# the default db. Thus, we set use-existing flag and manually create
# the default db, citus extension and the is_citus_depended_object
# function.
###
my $dbName = shift;
# prepare tablespace folder
rmdir "./testtablespace";
mkdir "./testtablespace";
# create default db
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $masterPort, '-U', $user, "-d", "postgres",
'-c', "CREATE DATABASE $dbName;")) == 0
or die "Could not create $dbName database on master";
# alter default db's lc_monetary to C
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $masterPort, '-U', $user, "-d", $dbName,
'-c', "ALTER DATABASE $dbName SET lc_monetary TO 'C';")) == 0
or die "Could not create $dbName database on master";
if ($vanillaDev)
{
return;
}
# create extension citus
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $masterPort, '-U', $user, "-d", $dbName,
'-c', "CREATE EXTENSION citus;")) == 0
or die "Could not create citus extension on master";
# we do not want to expose that udf other than vanilla tests
my $citus_depended_object_def = "CREATE OR REPLACE FUNCTION
pg_catalog.is_citus_depended_object(oid,oid)
RETURNS bool
LANGUAGE C
AS 'citus', \$\$is_citus_depended_object\$\$;";
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $masterPort, '-U', $user, "-d", $dbName,
'-c', $citus_depended_object_def)) == 0
or die "Could not create FUNCTION is_citus_depended_object on master";
}
# Finally run the tests
if ($vanillatest)
{
$ENV{PGHOST} = $host;
$ENV{PGPORT} = $masterPort;
$ENV{PGUSER} = $user;
$ENV{VANILLATEST} = "1";
my $dbName = "regression";
PrepForVanillaTest($dbName);
if (-f "$vanillaSchedule")
{
rmdir "./testtablespace";
mkdir "./testtablespace";
my $pgregressdir=catfile(dirname("$pgxsdir"), "regress");
$exitcode = system("$plainRegress", ("--inputdir", $pgregressdir),
("--schedule", catfile("$pgregressdir", "parallel_schedule")))
$exitcode = system("$plainRegress",
("--inputdir", $pgregressdir),
("--schedule", catfile("$pgregressdir", "parallel_schedule")),
("--use-existing"),
("--host","$host"),
("--port","$masterPort"),
("--user","$user"),
("--dbname", "$dbName"))
}
else
{
$exitcode = system("make", ("-C", catfile("$postgresBuilddir", "src", "test", "regress"), "installcheck-parallel"))
my $pgregressdir=catfile("$postgresSrcdir", "src", "test", "regress");
$exitcode = system("$plainRegress",
("--inputdir", $pgregressdir),
("--schedule", catfile("$pgregressdir", "parallel_schedule")),
("--use-existing"),
("--host","$host"),
("--port","$masterPort"),
("--user","$user"),
("--dbname", "$dbName"))
}
}
elsif ($isolationtester)

View File

@ -0,0 +1,151 @@
-- create the udf is_citus_depended_object that is needed for the tests
CREATE OR REPLACE FUNCTION
pg_catalog.is_citus_depended_object(oid,oid)
RETURNS bool
LANGUAGE C
AS 'citus', $$is_citus_depended_object$$;
-- execute tests in a separate namespace
CREATE SCHEMA citus_dependend_object;
SET search_path TO citus_dependend_object;
-- PG_CLASS VISIBILITY
-- check if we correctly determine whether a relation is citus dependent or not.
CREATE TABLE no_hide_pg_class(relname text);
CREATE TABLE hide_pg_class(relname text);
-- create a relation that depends on noderole type which is a citus object
CREATE TABLE citus_depended_class(nrole noderole);
-- create a relation that depends on columnar access method which is a citus object
CREATE TABLE citus_depended_class2(id int);
SELECT alter_table_set_access_method('citus_depended_class2', 'columnar');
-- create a relation that does not depend on citus
CREATE TABLE citus_independed_class(id int);
-- store all relations
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_class SELECT relname FROM pg_class;
-- store all relations except citus relations
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_class SELECT relname FROM pg_class;
-- prove that some relations are hidden or not
SELECT relname,
CASE
WHEN relname IN
(
SELECT relname FROM no_hide_pg_class
EXCEPT
SELECT relname FROM hide_pg_class
) THEN true
ELSE false
END AS is_hidden
FROM (VALUES ('pg_dist_shard'), ('pg_dist_placement'), ('pg_type'), ('pg_proc'),
('citus_depended_class'), ('citus_depended_class2'), ('citus_independed_class')) rels(relname);
-- PG_TYPE VISIBILITY
-- check if we correctly determine whether a type is citus dependent or not.
CREATE TABLE no_hide_pg_type(typname text);
CREATE TABLE hide_pg_type(typname text);
-- create a type that depends on noderole type which is a citus object
CREATE TYPE citus_depended_type AS (nrole noderole);
-- create a relation that does not depend on citus
CREATE TYPE citus_independed_type AS (id int);
-- store all types
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_type SELECT typname FROM pg_type;
-- store all types except citus types
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_type SELECT typname FROM pg_type;
-- prove that some types are hidden or not
SELECT typname,
CASE
WHEN typname IN
(
SELECT typname FROM no_hide_pg_type
EXCEPT
SELECT typname FROM hide_pg_type
) THEN true
ELSE false
END AS is_hidden
FROM (VALUES ('noderole'), ('_noderole'), ('int'), ('_int'),
('citus_depended_type'), ('citus_independed_type')) types(typname);
-- PG_AM VISIBILITY
-- check if we correctly determine whether an access method is citus dependent or not.
CREATE TABLE no_hide_pg_am(amname text);
CREATE TABLE hide_pg_am(amname text);
-- store all access methods
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_am SELECT amname FROM pg_am;
-- store all access methods except citus access methods
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_am SELECT amname FROM pg_am;
-- show all hidden access methods
SELECT amname AS hidden_am FROM no_hide_pg_am
EXCEPT
SELECT amname AS hidden_am FROM hide_pg_am
ORDER BY 1;
-- show all unhidden access methods
SELECT amname AS unhidden_am FROM no_hide_pg_am
EXCEPT
(
SELECT amname FROM no_hide_pg_am
EXCEPT
SELECT amname FROM hide_pg_am
)
ORDER BY 1;
-- PG_PROC VISIBILITY
-- check if we correctly determine whether a procedure is citus dependent or not.
CREATE TABLE no_hide_pg_proc(proname text);
CREATE TABLE hide_pg_proc(proname text);
-- create a procedure that depends on noderole type which is a citus object
CREATE OR REPLACE PROCEDURE citus_depended_proc(nrole noderole)
LANGUAGE SQL
AS $$
$$;
-- create a procedure that does not depend on citus
CREATE OR REPLACE PROCEDURE citus_independed_proc(id int)
LANGUAGE SQL
AS $$
$$;
-- store all access procedures
SET citus.hide_citus_dependent_objects TO false;
INSERT INTO no_hide_pg_proc SELECT proname FROM pg_proc;
-- store all access procedures except citus procedures
SET citus.hide_citus_dependent_objects TO true;
INSERT INTO hide_pg_proc SELECT proname FROM pg_proc;
-- prove that some procedures are hidden or not
SELECT proname,
CASE
WHEN proname IN
(
SELECT proname FROM no_hide_pg_proc
EXCEPT
SELECT proname FROM hide_pg_proc
) THEN true
ELSE false
END AS is_hidden
FROM (VALUES ('master_add_node'), ('format'),
('citus_depended_proc'), ('citus_independed_proc')) procs(proname);
-- drop the namespace with all its objects
DROP SCHEMA citus_dependend_object CASCADE;