命名空间
目录 pg_namespace 存储命名空间。命名空间是 SQL 模式的基础结构:每个命名空间都可以拥有独立的表、类型等集合,而不会发生名称冲突。
表结构
| 列 | 类型描述 |
|---|---|
oid |
oid行标识符 |
nspname |
name命名空间的名称 |
nspowner |
oid (引用 pg_authid.oid)命名空间的拥有者 |
nspacl |
aclitem[]访问权限;有关详细信息,请参阅 第 5.8 节 |
search_path
PostgreSQL: 文档: 18: 5.10. 模式 - PostgreSQL 数据库
相关内容可查看文档,search_path能简化在schema中创建类型或查找的方式,在默认设置下,search_path的值为:
search_path
--------------
"$user", public
第一个元素指定要搜索一个与当前用户同名的模式。如果不存在这样的模式,则忽略该条目。第二个元素指的是我们已经见过的 public 模式。
search_path 总体由以下全局变量实现,active表示正在使用的search_path,base表示从guc search_path解析出来的值
查询时,根据baseSearchPathValid判断当前search_path是否还有效,无效时会做recompute
区别active和base是因为有些情况的search_path是内核中临时push 覆盖的,并不与guc的值一致,实际当前已经很少允许直接push覆盖search_path,可能会有隐藏问题

c
// 当前正在使用的SearchPath
static List *activeSearchPath = NIL;
// 当前创建对象时用的Namespace,可以理解为用户指定searchpath的头节点
static Oid activeCreationNamespace = InvalidOid;
// 为true时,activeCreationNamespace应为temp_ns但是,临时命名空间还没set up,activeCreationNamespace的值无效
static bool activeTempCreationPending = false;
// SearchPath 迭代次数,每次变更时++,可以根据该值判断当前path是否和activeSearchPath一致
static uint64 activePathGeneration = 1;
// 解析自namespace_search_path的值,即guc search_path
static List *baseSearchPath = NIL;
static Oid baseCreationNamespace = InvalidOid;
static bool baseTempCreationPending = false;
static Oid namespaceUser = InvalidOid;
// 以上base值是否有效,无效的话要重新计算
static bool baseSearchPathValid = true;
怎么通过searchpath 查找类型或其他对象
遍历activeSearchPath,查找时添加过滤条件namespaceId,或是遍历对比返回tuple的namespaceId
c
Oid
TypenameGetTypidExtended(const char *typname, bool temp_ok)
{
Oid typid;
ListCell *l;
recomputeNamespacePath();
foreach(l, activeSearchPath)
{
Oid namespaceId = lfirst_oid(l);
if (!temp_ok && namespaceId == myTempNamespace)
continue; /* do not look in temp namespace */
typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
PointerGetDatum(typname),
ObjectIdGetDatum(namespaceId));
if (OidIsValid(typid))
return typid;
}
/* Not found in path */
return InvalidOid;
}
searchpath初始化
c
InitPostgres
InitializeSearchPath
void
InitializeSearchPath(void)
{
//Bootstrap 模式下searchpath只为pg_catalog,所有对象都在pg_catalog下创建
if (IsBootstrapProcessingMode())
{
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
baseSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE);
MemoryContextSwitchTo(oldcxt);
baseCreationNamespace = PG_CATALOG_NAMESPACE;
baseTempCreationPending = false;
baseSearchPathValid = true;
namespaceUser = GetUserId();
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
activePathGeneration++; /* pro forma */
}
else
{
// 正常情况, 注册失效函数,当pg_namespace, pg_auth表行改变时, 调用失效函数
// pg_auth表行改变 可能改用户名,涉及$user
CacheRegisterSyscacheCallback(NAMESPACEOID,
NamespaceCallback,
(Datum) 0);
CacheRegisterSyscacheCallback(AUTHOID,
NamespaceCallback,
(Datum) 0);
// 初始化, 强制search path下次调用时被重新计算
baseSearchPathValid = false;
}
}
static void
NamespaceCallback(Datum arg, int cacheid, uint32 hashvalue)
{
// 强制search path下次调用时被重新计算
baseSearchPathValid = false;
}
计算赋值searchpath
c
static void
recomputeNamespacePath(void)
{
//... 变量
// 当前激活的search_path为PushOverrideSearchPath 方式临时手动设置的,不需要重计算
if (overrideStack)
return;
// 当前search_path有效 不需要重计算
if (baseSearchPathValid && namespaceUser == roleid)
return;
//copy search_path的值
rawname = pstrdup(namespace_search_path);
//解析,分割符为','
if (!SplitIdentifierStringForSearchPath(rawname, ',', &namelist))
{
elog(ERROR, "invalid list syntax");
}
//namelist 转为oidlist
oidlist = NIL;
temp_missing = false;
foreach(l, namelist)
{
char *curname = (char *) lfirst(l);
Oid namespaceId;
if ((compatible_db == DB_ORACLE && enable_case_switch && identifier_case_switch != NORMAL) ?
pg_strcasecmp(curname, "$user") == 0 : strcmp(curname, "$user") == 0)
{
//当前值为$user,查询是否有用户名同名的ns,有 并且acl权限检查ok,添加到oidlist
HeapTuple tuple;
tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
if (HeapTupleIsValid(tuple))
{
char *rname;
rname = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname);
namespaceId = get_namespace_oid(rname, true);
ReleaseSysCache(tuple);
if (OidIsValid(namespaceId) &&
!list_member_oid(oidlist, namespaceId) &&
object_aclcheck(NamespaceRelationId, namespaceId, roleid,
ACL_USAGE) == ACLCHECK_OK &&
InvokeNamespaceSearchHook(namespaceId, false))
oidlist = lappend_oid(oidlist, namespaceId);
}
}
else if (strcmp(curname, "pg_temp") == 0)
{
//临时表空间, 存在则加到oidlist, 不存在且如果pg_temp为头节点,表示后续新的头节点不为CreationNamespace,先置temp_missing=true
if (OidIsValid(myTempNamespace))
{
if (!list_member_oid(oidlist, myTempNamespace) &&
InvokeNamespaceSearchHook(myTempNamespace, false))
oidlist = lappend_oid(oidlist, myTempNamespace);
}
else
{
if (oidlist == NIL)
temp_missing = true;
}
}
else
{
//其他值, 查询ns名,判断acl,添加到oidlist
namespaceId = get_namespace_oid(curname, true);
if (OidIsValid(namespaceId) &&
!list_member_oid(oidlist, namespaceId) &&
object_aclcheck(NamespaceRelationId, namespaceId, roleid,
ACL_USAGE) == ACLCHECK_OK &&
InvokeNamespaceSearchHook(namespaceId, false))
oidlist = lappend_oid(oidlist, namespaceId);
}
}
// 头节点, 后续可能作为CreationNamespace
if (oidlist == NIL)
firstNS = InvalidOid;
else
firstNS = linitial_oid(oidlist);
//添加隐式的ns到链表头, search时会优先查找这些ns, 创建对象时还是用上面的firstNS
//pg_catalog, ns1, ns2 ...
if (!list_member_oid(oidlist, PG_CATALOG_NAMESPACE))
oidlist = lcons_oid(PG_CATALOG_NAMESPACE, oidlist);
//pg_temp, pg_catalog, ns1, ns2 ...
if (OidIsValid(myTempNamespace) &&
!list_member_oid(oidlist, myTempNamespace))
oidlist = lcons_oid(myTempNamespace, oidlist);
//判断search_path是否有变更, 没有变更的话pathChanged = false, 不增加generation的数量
if (baseCreationNamespace == firstNS &&
baseTempCreationPending == temp_missing &&
equal(oidlist, baseSearchPath))
{
pathChanged = false;
}
else
{
//path变更,赋值给base
pathChanged = true;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
newpath = list_copy(oidlist);
MemoryContextSwitchTo(oldcxt);
list_free(baseSearchPath);
baseSearchPath = newpath;
baseCreationNamespace = firstNS;
baseTempCreationPending = temp_missing;
}
//标记basepath有效
baseSearchPathValid = true;
namespaceUser = roleid;
//置为active
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
//path改变 activePathGeneration++
if (pathChanged)
activePathGeneration++;
/* Clean up. */
pfree(rawname);
list_free(namelist);
list_free(oidlist);
}
什么情况下需要做recompute
从对象名->对象oid转化的过程,recompute函数也实际封装到了这类 对象名->对象oid转化的函数中,
对于dml或者查询语句,主要在语义分析阶段和重写阶段
对于ddl语句,分布在ddl的处理逻辑中,大多数场景同一事务内首次做了recompute,之后都是validate的状态,只需要做判断即可
c
pg_analyze_and_rewrite_fixedparams
parse_analyze_fixedparams
transformTopLevelStmt
transformOptionalSelectInto
transformStmt
transformFromClause
transformTableEntry
addRangeTableEntry
parserOpenTable
relation_openrv_extended
RangeVarGetRelid //宏同 RangeVarGetRelidExtended
RelnameGetRelid
plancache中保存的SearchPath
plancache用来缓存执行计划,当searchpath改变时,查询语句可能涉及对象变更,对应的执行计划也应不一致,plancache中保存了一份当时的SearchPath,用来验证当前执行计划是否还有效
c
//保存的结构体
typedef struct OverrideSearchPath
{
List *schemas; /* OIDs of explicitly named schemas */
bool addCatalog; /* implicitly prepend pg_catalog? */
bool addTemp; /* implicitly prepend temp schema? */
uint64 generation; /* for quick detection of equality to active */
} OverrideSearchPath;
c
//获取当前search_path
OverrideSearchPath *
GetOverrideSearchPath(MemoryContext context)
{
OverrideSearchPath *result;
List *schemas;
MemoryContext oldcxt;
recomputeNamespacePath();
oldcxt = MemoryContextSwitchTo(context);
result = (OverrideSearchPath *) palloc0(sizeof(OverrideSearchPath));
schemas = list_copy(activeSearchPath);
// 遍历schemas, 直到为activeCreationNamespace, 删除前几个隐式的ns
while (schemas && linitial_oid(schemas) != activeCreationNamespace)
{
if (linitial_oid(schemas) == myTempNamespace)
result->addTemp = true;
else
{
Assert(linitial_oid(schemas) == PG_CATALOG_NAMESPACE);
result->addCatalog = true;
}
schemas = list_delete_first(schemas);
}
result->schemas = schemas;
result->generation = activePathGeneration;
MemoryContextSwitchTo(oldcxt);
return result;
}
判断是否searchpath改变
c
bool
OverrideSearchPathMatchesCurrent(OverrideSearchPath *path)
{
ListCell *lc,
*lcp;
// 刷新SearchPath
recomputeNamespacePath();
//generation一致 即满足
if (path->generation == activePathGeneration)
return true;
//扫描SearchPath
lc = list_head(activeSearchPath);
//隐式的几个ns:tmpns,pg_catalog
if (path->addTemp)
{
if (lc && lfirst_oid(lc) == myTempNamespace)
lc = lnext(activeSearchPath, lc);
else
return false;
}
//判断activeCreationNamespace 是否一致
if (activeCreationNamespace != (lc ? lfirst_oid(lc) : InvalidOid))
return false;
//遍历判断剩余ns
foreach(lcp, path->schemas)
{
if (lc && lfirst_oid(lc) == lfirst_oid(lcp))
lc = lnext(activeSearchPath, lc);
else
return false;
}
if (lc)
return false;
//更新generation,加速下次判断
path->generation = activePathGeneration;
return true;
}
临时表
临时表通过关联ns实现,每个会话有自己独立的tmp_ns,在事务提交时级联删除ns中的内容,或者会话结束时删除,实现临时表的删除
c
// 初始化temp_ns, 只有在创建临时表时才会置force为true, 读表时如果没有temp_ns,同时也说明没有临时表
static void
AccessTempTableNamespace(bool force)
{
/*
* Make note that this temporary namespace has been accessed in this
* transaction.
*/
MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
//只有在创建临时表时才会置force为true
if (!force && OidIsValid(myTempNamespace))
return;
/*
* The temporary tablespace does not exist yet and is wanted, so
* initialize it.
*/
InitTempTableNamespace();
}
//初始化tmp_ns
static void
InitTempTableNamespace(void)
{
char namespaceName[NAMEDATALEN];
Oid namespaceId;
Oid toastspaceId;
Assert(!OidIsValid(myTempNamespace));
//1. 权限检查
if (object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(),
ACL_CREATE_TEMP) != ACLCHECK_OK)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create temporary tables in database \"%s\"",
get_database_name(MyDatabaseId))));
//2. 备份恢复,并行worker 不创建临时表
if (RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
errmsg("cannot create temporary tables during recovery")));
if (IsParallelWorker())
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
errmsg("cannot create temporary tables during a parallel operation")));
//3. 创建临时表, 临时表ns名 为pg_temp_+backendid (backendid是可复用的, 所以即使退出会话也不会删除ns,只是删除ns里的内容)
// 如果ns存在则级联删除里面的内容
snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyBackendId);
namespaceId = get_namespace_oid(namespaceName, true);
if (!OidIsValid(namespaceId))
{
/*
* First use of this temp namespace in this database; create it. The
* temp namespaces are always owned by the superuser. We leave their
* permissions at default --- i.e., no access except to superuser ---
* to ensure that unprivileged users can't peek at other backends'
* temp tables. This works because the places that access the temp
* namespace for my own backend skip permissions checks on it.
*/
namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
true);
/* Advance command counter to make namespace visible */
CommandCounterIncrement();
}
else
{
/*
* If the namespace already exists, clean it out (in case the former
* owner crashed without doing so).
*/
RemoveTempRelations(namespaceId);
}
//4. 创建toast temp ns,同创建临时表ns, 如果ns存在不用做级联删除, 假设之前的级联删除同时也会删除toast表
snprintf(namespaceName, sizeof(namespaceName), "pg_toast_temp_%d",
MyBackendId);
toastspaceId = get_namespace_oid(namespaceName, true);
if (!OidIsValid(toastspaceId))
{
toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID,
true);
/* Advance command counter to make namespace visible */
CommandCounterIncrement();
}
// 5.赋值全局变量, inValid searchpath
myTempNamespace = namespaceId;
myTempToastNamespace = toastspaceId;
/*
* Mark MyProc as owning this namespace which other processes can use to
* decide if a temporary namespace is in use or not. We assume that
* assignment of namespaceId is an atomic operation. Even if it is not,
* the temporary relation which resulted in the creation of this temporary
* namespace is still locked until the current transaction commits, and
* its pg_namespace row is not visible yet. However it does not matter:
* this flag makes the namespace as being in use, so no objects created on
* it would be removed concurrently.
*/
MyProc->tempNamespaceId = namespaceId;
/* It should not be done already. */
Assert(myTempNamespaceSubID == InvalidSubTransactionId);
myTempNamespaceSubID = GetCurrentSubTransactionId();
baseSearchPathValid = false; /* need to rebuild list */
}
//事务结束对ns的处理
void
AtEOXact_Namespace(bool isCommit, bool parallel)
{
// 创建了临时表ns
//1. 事务提交 注册shmem_exit 回调函数,会话退出时清理临时表
//2. 事务回滚,ns创建也回滚, search_path改变, 置baseSearchPathValid为force, reset临时表全局变量
if (myTempNamespaceSubID != InvalidSubTransactionId && !parallel)
{
if (isCommit)
before_shmem_exit(RemoveTempRelationsCallback, 0);
else
{
myTempNamespace = InvalidOid;
myTempToastNamespace = InvalidOid;
baseSearchPathValid = false; /* need to rebuild list */
MyProc->tempNamespaceId = InvalidOid;
}
myTempNamespaceSubID = InvalidSubTransactionId;
}
//清理push进去的search_path
if (overrideStack)
{
if (isCommit)
elog(WARNING, "leaked override search path");
while (overrideStack)
{
OverrideStackEntry *entry;
entry = (OverrideStackEntry *) linitial(overrideStack);
overrideStack = list_delete_first(overrideStack);
list_free(entry->searchPath);
pfree(entry);
}
/* If not baseSearchPathValid, this is useless but harmless */
activeSearchPath = baseSearchPath;
activeCreationNamespace = baseCreationNamespace;
activeTempCreationPending = baseTempCreationPending;
/* Always bump generation --- see note in recomputeNamespacePath */
activePathGeneration++;
}
}
//级联删除ns中内容,不包含ns本身
static void
RemoveTempRelations(Oid tempNamespaceId)
{
ObjectAddress object;
/*
* We want to get rid of everything in the target namespace, but not the
* namespace itself (deleting it only to recreate it later would be a
* waste of cycles). Hence, specify SKIP_ORIGINAL. It's also an INTERNAL
* deletion, and we want to not drop any extensions that might happen to
* own temp objects.
*/
object.classId = NamespaceRelationId;
object.objectId = tempNamespaceId;
object.objectSubId = 0;
performDeletion(&object, DROP_CASCADE,
PERFORM_DELETION_INTERNAL |
PERFORM_DELETION_QUIETLY |
PERFORM_DELETION_SKIP_ORIGINAL |
PERFORM_DELETION_SKIP_EXTENSIONS);
}
//会话退出的回调函数
static void
RemoveTempRelationsCallback(int code, Datum arg)
{
if (OidIsValid(myTempNamespace)) /* should always be true */
{
/* Need to ensure we have a usable transaction. */
AbortOutOfAnyTransaction();
StartTransactionCommand();
PushActiveSnapshot(GetTransactionSnapshot());
RemoveTempRelations(myTempNamespace);
PopActiveSnapshot();
CommitTransactionCommand();
}
}
/*
* Remove all temp tables from the temporary namespace.
*/
void
ResetTempTableNamespace(void)
{
if (OidIsValid(myTempNamespace))
RemoveTempRelations(myTempNamespace);
}