前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(48)namespace计算逻辑分析(不显示指定namespace)

Postgresql源码(48)namespace计算逻辑分析(不显示指定namespace)

作者头像
mingjie
发布2022-05-25 10:52:39
8990
发布2022-05-25 10:52:39
举报
文章被收录于专栏:Postgresql源码分析

搜索优先级

默认情况下查询一张表,不指定namespace,系统查询的是哪个namespace?

"$user", public场景来说,搜索顺序:

1、临时表 2、系统表 3、普通表

(原因见下面recomputeNamespacePath函数分析)

代码语言:javascript
复制
postgres=# show search_path ;
   search_path   
-----------------
 "$user", public

-- 优先级1:临时表
-- create temp table pg_class(i int);
pg_temp_3.pg_class

-- 优先级2:pg_catalog
pg_catalog.pg_class

-- 优先级3:普通表
-- create table pg_class(a int);
public.pg_class

namespace核心全局变量

1 规律

namespace大部分逻辑都是在维护、使用三个全局变量:

  1. 搜索空间:activeSearchPath
  2. 创建空间:activeCreationNamespace
  3. 临时空间:activeTempCreationPending

三个全局变量有两套赋值逻辑:

第一套:计算好baseXXX,然后统一赋值给activeXXX,相当于激活使用base

代码语言:javascript
复制
		activeSearchPath = baseSearchPath;
		activeCreationNamespace = baseCreationNamespace;
		activeTempCreationPending = baseTempCreationPending;

第二套:用临时值给三个变量赋值,相当于临时切换到另一套查找空间,用完了可以切换回第一套

代码语言:javascript
复制
	activeSearchPath = entry->searchPath;
	activeCreationNamespace = entry->creationNamespace;
	activeTempCreationPending = false;	/* XXX is this OK? */

注意:

  • namespace的所有工具函数都会直接使用activeXXX激活的全局变量
  • recomputeNamespacePath函数负责计算他们

2 细节

通常使用namespace_search_path:$1 = 0x282df20 "\"$user\", public"(文本)来搜索命名空间。

对于某些特殊代码区域,配置override stack可以忽略namespace_search_path。

真正用于搜索使用的是activeSearchPath(List),activeSearchPath指向只有两种情况

情况一:指向override stack堆顶

情况二:指向baseSearchPath(List,从namespace_search_path构造而来)

activeSearchPath长什么样?

代码语言:javascript
复制
activeSearchPath
(gdb) p activeSearchPath->elements[0]->oid_value
$9 = 24709
(gdb) p activeSearchPath->elements[1]->oid_value
$10 = 11
(gdb) p activeSearchPath->elements[2]->oid_value
$11 = 2200
  • baseSearchPath的可用性由baseSearchPathValid(Bool)表示,如果失效需要重新从namespace_search_path字符串计算。
  • 什么时候失效?
    • 情况一:namespace_search_path变了
    • 情况二:pg_namespace的syscache失效
  • 什么时候重新算?
    • override查找时重新计算

baseSearchPath长什么样?

代码语言:javascript
复制
(gdb) p baseSearchPath->elements[0]->oid_value
$14 = 24709
(gdb) p baseSearchPath->elements[1]->oid_value
$15 = 11
(gdb) p baseSearchPath->elements[2]->oid_value
$16 = 2200
  • activeSearchPath/activeCreationNamespace/activeTempCreationPending变化时+1
  • 用于快速判断上述值是否改变

核心函数分析:recomputeNamespacePath

recomputeNamespacePath在namespace.c中有20多次调用,下面分析执行流程:

SQL执行中打开任意表文件执行RelnameGetRelid时,会第一次执行recomputeNamespacePath,后面还会执行多次recomputeNamespacePath但不会重新计算了。

第一次执行总结:

  • 【1】第一次进函数时,baseSearchPathValid是false,需要重新计算baseSearchPath(List)。注意这里还有一个判断当前用户是不是切换了,切换了也要重新刷baseSearchPath。一般这是namespace_search_path字符串都有值,就是search_path,baseSearchPath就是从这个字符串计算出来的。
  • 【2】namespace_search_path字符串组装到namelist中"\"
  • 【3】开始解析namelist 当前【user】如果是user有同名的namespace在pg_namespace中,可以查到。但是一般我们只创建用户,不会默认带一个同名namespace的,所以这里经常查出来oid=0,不会记录到结果集中当前【public】在pg_namespace中查询到OID2200记录到oidlist中
  • 【4】给了一个普通的字符串,表示namespace,查pg_namespace,在系统表中确有指定名称,oid记录到结果中
  • 【5】把PG_CATALOG加到最前面
  • 【6】把私有tmp表空间加到最前面
  • 【7】三个全局变量赋值:baseSearchPath就是上面拼接的oidlist、baseCreationNamespace=第一个nsoid

到这里,全局变量的值用namespace_search_path重新计算了一遍。

  • 【8】上面计算完了,真正激活,给activeXXX赋值,其他工具函数只会用active的值。
代码语言:javascript
复制
static void
recomputeNamespacePath(void)
{
  ...

// overrideStack优先级最高,有的话直接使用,不做任何更新
	if (overrideStack)
		return;

//【1】
	if (baseSearchPathValid && namespaceUser == roleid)
		return;

// $2 = 0x28125a0 "\"$user\", public"
	rawname = pstrdup(namespace_search_path);

//【2】
// p (char*)namelist->elements[0]->ptr_value
// $11 = 0x28125a1 "$user"
// p (char*)namelist->elements[1]->ptr_value
// $12 = 0x28125a9 "public"
	SplitIdentifierString(rawname, ',', &namelist)
  ...

  // 解析List的结果集
	oidlist = NIL;
	temp_missing = false;
	foreach(l, namelist)
  /*
    (gdb) p (char*)namelist->elements[0]->ptr_value
    $22 = 0x28125a1 "$user"
    (gdb) p (char*)namelist->elements[1]->ptr_value
    $23 = 0x28125a9 "public"
  */
	{
		char	   *curname = (char *) lfirst(l);
		Oid			namespaceId;

		if (strcmp(curname, "$user") == 0)
		{
			HeapTuple	tuple;

			tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
			if (HeapTupleIsValid(tuple))
			{
				char	   *rname;
        // 查系统表pg_authid,查到了,确实有这个用户
				rname = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname);
        
        // 用户名mingjiegao去pg_namespace里面查不到!
        // namespaceId = 0
				namespaceId = get_namespace_oid(rname, true);
				ReleaseSysCache(tuple);
				// 先做权限检查acl check ...
        // 【3】记录到结果中,注意只记录OID即可
				oidlist = lappend_oid(oidlist, namespaceId);
			}
		}
		else if (strcmp(curname, "pg_temp") == 0)
		{
			if (OidIsValid(myTempNamespace))
			{
				if (!list_member_oid(oidlist, myTempNamespace) &&
					InvokeNamespaceSearchHook(myTempNamespace, false))
					oidlist = lappend_oid(oidlist, myTempNamespace);
			}
			else
			{
				/* If it ought to be the creation namespace, set flag */
				if (oidlist == NIL)
					temp_missing = true;
			}
		}
		else
		{
			// 【4】给了一个普通的字符串,表示namespace,这里查pg_namespace
			namespaceId = get_namespace_oid(curname, true);
			if (OidIsValid(namespaceId) &&
				!list_member_oid(oidlist, namespaceId) &&
				pg_namespace_aclcheck(namespaceId, roleid,
									  ACL_USAGE) == ACLCHECK_OK &&
				InvokeNamespaceSearchHook(namespaceId, false))
        // 在系统表中确有指定名称,oid记录到结果中
				oidlist = lappend_oid(oidlist, namespaceId);
		}
	}

  // 记录第一个解析出来的oid
  // public 2200
	if (oidlist == NIL)
		firstNS = InvalidOid;
	else
		firstNS = linitial_oid(oidlist);
  
  // 【5】把PG_CATALOG加到最前面
  //  (gdb) p oidlist->elements[0]->oid_value
  // $16 = 11
	if (!list_member_oid(oidlist, PG_CATALOG_NAMESPACE))
		oidlist = lcons_oid(PG_CATALOG_NAMESPACE, oidlist);

  // 【6】把私有tmp表空间加到最前面
  // (如果有)
	if (OidIsValid(myTempNamespace) &&
		!list_member_oid(oidlist, myTempNamespace))
		oidlist = lcons_oid(myTempNamespace, oidlist);

  // 如果值都是正确的,不在进行重新赋值,避免值拷贝
	if (baseCreationNamespace == firstNS &&
		baseTempCreationPending == temp_missing &&
		equal(oidlist, baseSearchPath))
	{
		pathChanged = false;
	}
	else
	{
    //【7】三个全局变量赋值:baseSearchPath就是上面拼接的oidlist、baseCreationNamespace=第一个nsoid
		pathChanged = true;

		/* Must save OID list in permanent storage. */
		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
		newpath = list_copy(oidlist);
		MemoryContextSwitchTo(oldcxt);

		/* Now safe to assign to state variables. */
		list_free(baseSearchPath);
		baseSearchPath = newpath;
		baseCreationNamespace = firstNS;
		baseTempCreationPending = temp_missing;
	}

	/* Mark the path valid. */
	baseSearchPathValid = true;
	namespaceUser = roleid;

	//【8】上面计算完了,真正激活使用是在这里。
  // 其他工具函数只会用active的值。
	activeSearchPath = baseSearchPath;
	activeCreationNamespace = baseCreationNamespace;
	activeTempCreationPending = baseTempCreationPending;

	/*
	 * Bump the generation only if something actually changed.  (Notice that
	 * what we compared to was the old state of the base path variables; so
	 * this does not deal with the situation where we have just popped an
	 * override path and restored the prior state of the base path.  Instead
	 * we rely on the override-popping logic to have bumped the generation.)
	 */
	if (pathChanged)
		activePathGeneration++;

	/* Clean up. */
	pfree(rawname);
	list_free(namelist);
	list_free(oidlist);
}

工具函数举例:RangeVarGetCreationNamespace

创建对象时会用到RangeVarGetCreationNamespace

例如我创建表的时候没有指定namespace,那么创建表时就会有这样的参数进入RangeVarGetCreationNamespace:

代码语言:javascript
复制
(gdb) p *newRelation
$20 = {type = T_RangeVar, catalogname = 0x0, schemaname = 0x0, relname = 0x2811ed8 "sss", inh = true, relpersistence = 112 'p', alias = 0x0, location = 13}

执行路径总结起来就是使用activeCreationNamespace的值作为namespaceId返回。

代码语言:javascript
复制
Oid
RangeVarGetCreationNamespace(const RangeVar *newRelation)
{
	Oid			namespaceId;

	/*
	 * We check the catalog name and then ignore it.
	 */
	if (newRelation->catalogname)
	{
		if (strcmp(newRelation->catalogname, get_database_name(MyDatabaseId)) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
							newRelation->catalogname, newRelation->schemaname,
							newRelation->relname)));
	}

	if (newRelation->schemaname)
	{
...
	}
	else if (newRelation->relpersistence == RELPERSISTENCE_TEMP)
	{
...
	}
	else
	{
		/* use the default creation namespace */
		recomputeNamespacePath();
		if (activeTempCreationPending)
		{
			/* Need to initialize temp namespace */
			AccessTempTableNamespace(true);
			return myTempNamespace;
		}
		namespaceId = activeCreationNamespace;
		if (!OidIsValid(namespaceId))
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_SCHEMA),
					 errmsg("no schema has been selected to create in")));
	}

	/* Note: callers will check for CREATE rights when appropriate */

	return namespaceId;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 搜索优先级
  • namespace核心全局变量
    • 1 规律
      • 2 细节
      • 核心函数分析:recomputeNamespacePath
      • 工具函数举例:RangeVarGetCreationNamespace
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档