/* * This variant of list_append_unique() operates upon lists of OIDs. */ List * list_append_unique_oid(List *list, Oid datum) { if (list_member_oid(list, datum)) return list; else return lappend_oid(list, datum); }
/* * This variant of list_concat_unique() operates upon lists of OIDs. */ List * list_concat_unique_oid(List *list1, List *list2) { ListCell *cell; Assert(IsOidList(list1)); Assert(IsOidList(list2)); foreach(cell, list2) { if (!list_member_oid(list1, lfirst_oid(cell))) list1 = lappend_oid(list1, lfirst_oid(cell)); } check_list_invariants(list1); return list1; }
/* * Returns true if given object (operator/function/type) is shippable * according to the server options. * * Right now "shippability" is exclusively a function of whether the object * belongs to an extension declared by the user. In the future we could * additionally have a whitelist of functions/operators declared one at a time. */ static bool lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo) { Oid extensionOid; /* * Is object a member of some extension? (Note: this is a fairly * expensive lookup, which is why we try to cache the results.) */ extensionOid = getExtensionOfObject(classId, objectId); /* If so, is that extension in fpinfo->shippable_extensions? */ if (OidIsValid(extensionOid) && list_member_oid(fpinfo->shippable_extensions, extensionOid)) return true; return false; }
/* * This variant of list_union() operates upon lists of OIDs. */ List * list_union_oid(const List *list1, const List *list2) { List *result; const ListCell *cell; Assert(IsOidList(list1)); Assert(IsOidList(list2)); result = list_copy(list1); foreach(cell, list2) { if (!list_member_oid(result, lfirst_oid(cell))) result = lappend_oid(result, lfirst_oid(cell)); } check_list_invariants(result); return result; }
/* * This variant of list_difference() operates upon lists of OIDs. */ List * list_difference_oid(const List *list1, const List *list2) { const ListCell *cell; List *result = NIL; Assert(IsOidList(list1)); Assert(IsOidList(list2)); if (list2 == NIL) return list_copy(list1); foreach(cell, list1) { if (!list_member_oid(list2, lfirst_oid(cell))) result = lappend_oid(result, lfirst_oid(cell)); } check_list_invariants(result); return result; }
/* * Validator for a hash opclass. * * Some of the checks done here cover the whole opfamily, and therefore are * redundant when checking each opclass in a family. But they don't run long * enough to be much of a problem, so we accept the duplication rather than * complicate the amvalidate API. */ bool hashvalidate(Oid opclassoid) { bool result = true; HeapTuple classtup; Form_pg_opclass classform; Oid opfamilyoid; Oid opcintype; char *opclassname; HeapTuple familytup; Form_pg_opfamily familyform; char *opfamilyname; CatCList *proclist, *oprlist; List *grouplist; OpFamilyOpFuncGroup *opclassgroup; List *hashabletypes = NIL; int i; ListCell *lc; /* Fetch opclass information */ classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); if (!HeapTupleIsValid(classtup)) elog(ERROR, "cache lookup failed for operator class %u", opclassoid); classform = (Form_pg_opclass) GETSTRUCT(classtup); opfamilyoid = classform->opcfamily; opcintype = classform->opcintype; opclassname = NameStr(classform->opcname); /* Fetch opfamily information */ familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid)); if (!HeapTupleIsValid(familytup)) elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid); familyform = (Form_pg_opfamily) GETSTRUCT(familytup); opfamilyname = NameStr(familyform->opfname); /* Fetch all operators and support functions of the opfamily */ oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); /* Check individual support functions */ for (i = 0; i < proclist->n_members; i++) { HeapTuple proctup = &proclist->members[i]->tuple; Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); /* * All hash functions should be registered with matching left/right * types */ if (procform->amproclefttype != procform->amprocrighttype) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" contains support procedure %s with cross-type registration", opfamilyname, format_procedure(procform->amproc)))); result = false; } /* Check procedure numbers and function signatures */ switch (procform->amprocnum) { case HASHPROC: if (!check_hash_func_signature(procform->amproc, INT4OID, procform->amproclefttype)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" contains function %s with wrong signature for support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; } else { /* Remember which types we can hash */ hashabletypes = list_append_unique_oid(hashabletypes, procform->amproclefttype); } break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" contains function %s with invalid support number %d", opfamilyname, format_procedure(procform->amproc), procform->amprocnum))); result = false; break; } } /* Check individual operators */ for (i = 0; i < oprlist->n_members; i++) { HeapTuple oprtup = &oprlist->members[i]->tuple; Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); /* Check that only allowed strategy numbers exist */ if (oprform->amopstrategy < 1 || oprform->amopstrategy > HTMaxStrategyNumber) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" contains operator %s with invalid strategy number %d", opfamilyname, format_operator(oprform->amopopr), oprform->amopstrategy))); result = false; } /* hash doesn't support ORDER BY operators */ if (oprform->amoppurpose != AMOP_SEARCH || OidIsValid(oprform->amopsortfamily)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" contains invalid ORDER BY specification for operator %s", opfamilyname, format_operator(oprform->amopopr)))); result = false; } /* Check operator signature --- same for all hash strategies */ if (!check_amop_signature(oprform->amopopr, BOOLOID, oprform->amoplefttype, oprform->amoprighttype)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" contains operator %s with wrong signature", opfamilyname, format_operator(oprform->amopopr)))); result = false; } /* There should be relevant hash procedures for each datatype */ if (!list_member_oid(hashabletypes, oprform->amoplefttype) || !list_member_oid(hashabletypes, oprform->amoprighttype)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" lacks support function for operator %s", opfamilyname, format_operator(oprform->amopopr)))); result = false; } } /* Now check for inconsistent groups of operators/functions */ grouplist = identify_opfamily_groups(oprlist, proclist); opclassgroup = NULL; foreach(lc, grouplist) { OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); /* Remember the group exactly matching the test opclass */ if (thisgroup->lefttype == opcintype && thisgroup->righttype == opcintype) opclassgroup = thisgroup; /* * Complain if there seems to be an incomplete set of operators for * this datatype pair (implying that we have a hash function but no * operator). */ if (thisgroup->operatorset != (1 << HTEqualStrategyNumber)) { ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("hash operator family \"%s\" is missing operator(s) for types %s and %s", opfamilyname, format_type_be(thisgroup->lefttype), format_type_be(thisgroup->righttype)))); result = false; } }
/* * Alter table space move * * Allows a user to move all of their objects in a given tablespace in the * current database to another tablespace. Only objects which the user is * considered to be an owner of are moved and the user must have CREATE rights * on the new tablespace. These checks should mean that ALTER TABLE will never * fail due to permissions, but note that permissions will also be checked at * that level. Objects can be ALL, TABLES, INDEXES, or MATERIALIZED VIEWS. * * All to-be-moved objects are locked first. If NOWAIT is specified and the * lock can't be acquired then we ereport(ERROR). */ Oid AlterTableSpaceMove(AlterTableSpaceMoveStmt *stmt) { List *relations = NIL; ListCell *l; ScanKeyData key[1]; Relation rel; HeapScanDesc scan; HeapTuple tuple; Oid orig_tablespaceoid; Oid new_tablespaceoid; List *role_oids = roleNamesToIds(stmt->roles); /* Ensure we were not asked to move something we can't */ if (!stmt->move_all && stmt->objtype != OBJECT_TABLE && stmt->objtype != OBJECT_INDEX && stmt->objtype != OBJECT_MATVIEW) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("only tables, indexes, and materialized views exist in tablespaces"))); /* Get the orig and new tablespace OIDs */ orig_tablespaceoid = get_tablespace_oid(stmt->orig_tablespacename, false); new_tablespaceoid = get_tablespace_oid(stmt->new_tablespacename, false); /* Can't move shared relations in to or out of pg_global */ /* This is also checked by ATExecSetTableSpace, but nice to stop earlier */ if (orig_tablespaceoid == GLOBALTABLESPACE_OID || new_tablespaceoid == GLOBALTABLESPACE_OID) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot move relations in to or out of pg_global tablespace"))); /* * Must have CREATE rights on the new tablespace, unless it is the * database default tablespace (which all users implicitly have CREATE * rights on). */ if (OidIsValid(new_tablespaceoid) && new_tablespaceoid != MyDatabaseTableSpace) { AclResult aclresult; aclresult = pg_tablespace_aclcheck(new_tablespaceoid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_TABLESPACE, get_tablespace_name(new_tablespaceoid)); } /* * Now that the checks are done, check if we should set either to * InvalidOid because it is our database's default tablespace. */ if (orig_tablespaceoid == MyDatabaseTableSpace) orig_tablespaceoid = InvalidOid; if (new_tablespaceoid == MyDatabaseTableSpace) new_tablespaceoid = InvalidOid; /* no-op */ if (orig_tablespaceoid == new_tablespaceoid) return new_tablespaceoid; /* * Walk the list of objects in the tablespace and move them. This will * only find objects in our database, of course. */ ScanKeyInit(&key[0], Anum_pg_class_reltablespace, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(orig_tablespaceoid)); rel = heap_open(RelationRelationId, AccessShareLock); scan = heap_beginscan_catalog(rel, 1, key); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { Oid relOid = HeapTupleGetOid(tuple); Form_pg_class relForm; relForm = (Form_pg_class) GETSTRUCT(tuple); /* * Do not move objects in pg_catalog as part of this, if an admin * really wishes to do so, they can issue the individual ALTER * commands directly. * * Also, explicitly avoid any shared tables, temp tables, or TOAST * (TOAST will be moved with the main table). */ if (IsSystemNamespace(relForm->relnamespace) || relForm->relisshared || isAnyTempNamespace(relForm->relnamespace) || relForm->relnamespace == PG_TOAST_NAMESPACE) continue; /* Only consider objects which live in tablespaces */ if (relForm->relkind != RELKIND_RELATION && relForm->relkind != RELKIND_INDEX && relForm->relkind != RELKIND_MATVIEW) continue; /* Check if we were asked to only move a certain type of object */ if (!stmt->move_all && ((stmt->objtype == OBJECT_TABLE && relForm->relkind != RELKIND_RELATION) || (stmt->objtype == OBJECT_INDEX && relForm->relkind != RELKIND_INDEX) || (stmt->objtype == OBJECT_MATVIEW && relForm->relkind != RELKIND_MATVIEW))) continue; /* Check if we are only moving objects owned by certain roles */ if (role_oids != NIL && !list_member_oid(role_oids, relForm->relowner)) continue; /* * Handle permissions-checking here since we are locking the tables * and also to avoid doing a bunch of work only to fail part-way. * Note that permissions will also be checked by AlterTableInternal(). * * Caller must be considered an owner on the table to move it. */ if (!pg_class_ownercheck(relOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, NameStr(relForm->relname)); if (stmt->nowait && !ConditionalLockRelationOid(relOid, AccessExclusiveLock)) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("aborting due to \"%s\".\"%s\" --- lock not available", get_namespace_name(relForm->relnamespace), NameStr(relForm->relname)))); else LockRelationOid(relOid, AccessExclusiveLock); /* Add to our list of objects to move */ relations = lappend_oid(relations, relOid); } heap_endscan(scan); heap_close(rel, AccessShareLock); if (relations == NIL) ereport(NOTICE, (errcode(ERRCODE_NO_DATA_FOUND), errmsg("no matching relations in tablespace \"%s\" found", orig_tablespaceoid == InvalidOid ? "(database default)" : get_tablespace_name(orig_tablespaceoid)))); /* Everything is locked, loop through and move all of the relations. */ foreach(l, relations) { List *cmds = NIL; AlterTableCmd *cmd = makeNode(AlterTableCmd); cmd->subtype = AT_SetTableSpace; cmd->name = stmt->new_tablespacename; cmds = lappend(cmds, cmd); AlterTableInternal(lfirst_oid(l), cmds, false); }