PostgreSQL源码分析 —— CAST类型转换

我们分析一下PostgreSQL中显式类型转换是如何实现的,在PostgreSQL中,显式类型转换可以用CAST(x AS typename) 等同于 x::typename。 这里我们以select cast (2 as numeric为例进行分析。

再结合之前对Postgres源码分析------CREATE CAST的分析,基本就弄清了一部分PostgreSQL中数据类型转换问题。当然要完整的理解类型转换还需要完整的理解PostgreSQL中的类型系统,后续还需要我们逐步的深入理解。

源码分析

在分析类型转换前,我们前面分析过CREATE CAST的源码,类型转换实质就是定义个类型转换函数,存储在pg_cast系统表中。

sql 复制代码
CREATE CAST (source_type AS target_type)
    WITH FUNCTION function_name [ (argument_type [, ...]) ]
    [ AS ASSIGNMENT | AS IMPLICIT ]

在进行类型转换时会调用这个定义的函数。理解了这个我们再继续分析下面的源码。

主流程
c 复制代码
exec_simple_query
--> pg_parse_query   // 语法解析,这块不再做说明,前面的很多文章中已经讲的比较多了
--> pg_analyze_and_rewrite
    --> parse_analyze
        --> transformStmt
            --> transformSelectStmt
                --> transformTargetList
                    --> transformTargetEntry
                        --> transformTypeCast // 将TypeCast节点转为FuncExpr节点,调用函数为类型转换函数
                            --> typenameTypeIdAndMod // 类型名获取类型oid,访问pg_type系统表
                            --> exprType // 获取输入表达式的类型oid
                            --> coerce_to_target_type  // 类型转换函数
                                --> can_coerce_type  // 判断能否进行类型转换
                                    --> find_coercion_pathway // 查pg_cast系统表,能否被转换
                                --> coerce_type  // 将表达式转换另一个类型
                                    --> find_coercion_pathway
                                    --> getBaseTypeAndTypmod
                                    --> build_coercion_expression // 构造强制转换表达式节点
                                        --> makeFuncExpr // 构造类型转换函数的调用节点
                                --> coerce_type_typmod
--> pg_plan_queries // 生成执行计划
    --> pg_plan_query
        --> standard_planner
            --> subquery_planner
                --> preprocess_expression  // 预处理targetList 的表达式
                    --> eval_const_expressions  // 常量表达式化简
                        --> simplify_function
                            --> evaluate_function
                                --> evaluate_expr
                                    --> ExecEvalExprSwitchContext
                                        --> ExecInterpExpr
                                            --> int4_numeric // 调用最终的类型转换函数
            --> create_plan
语法解析方面

这里select cast( 2 as numeric),构造SelectStmt,cast为target部分,构造ResTarget,再构造TypeCast节点。因为这部分前面的文章中分析的比较多了,这里不再叙述。SelectStmt->ResTarget->TypeCast 语法解析树表示。我们重点看一下TypeCast的定义:

c 复制代码
/* TypeCast - a CAST expression */
typedef struct TypeCast
{
	NodeTag		type;
	Node	   *arg;			/* the expression being casted */ // 需要被转换的表达式
	TypeName   *typeName;		/* the target type */  // 转换成什么类型
	int			location;		/* token location, or -1 if unknown */
} TypeCast;

cast类型转换语法表示如下:

c 复制代码
c_expr:		columnref { $$ = $1; }
			| func_expr
				{ $$ = $1; }
func_expr: 			
            | func_expr_common_subexpr
				{ $$ = $1; }
func_expr_common_subexpr:
			| CAST '(' a_expr AS Typename ')'
				{ $$ = makeTypeCast($3, $5, @1); }
			| EXTRACT '(' extract_list ')'
				{
					$$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1);
				}

构造TypeCast节点:

c 复制代码
static Node *makeTypeCast(Node *arg, TypeName *typename, int location)
{
	TypeCast *n = makeNode(TypeCast);
	n->arg = arg;
	n->typeName = typename;
	n->location = location;
	return (Node *) n;
}
语义分析阶段

主流程:

c 复制代码
exec_simple_query
--> pg_parse_query   // 语法解析,这块不再做说明,前面的很多文章中已经讲的比较多了
--> pg_analyze_and_rewrite
    --> parse_analyze
        --> transformStmt
            --> transformSelectStmt
                --> transformTargetList
                    --> transformTargetEntry
                        --> transformTypeCast

主要是transformTypeCast函数实现,前面的SelectStmt转换为QueryResTarget转换为TargetEntry,这里的TypeCast转换为

c 复制代码
/*
 * Handle an explicit CAST construct.
 *
 * Transform the argument, look up the type name, and apply any necessary
 * coercion function(s).
 */
static Node *
transformTypeCast(ParseState *pstate, TypeCast *tc)
{
	Node	   *result;
	Node	   *arg = tc->arg;
	Node	   *expr;
	Oid			inputType;
	Oid			targetType;
	int32		targetTypmod;
	int			location;

	/* Look up the type name first */
	typenameTypeIdAndMod(pstate, tc->typeName, &targetType, &targetTypmod);

	/*
	 * Look through any AEXPR_PAREN nodes that may have been inserted thanks
	 * to operator_precedence_warning.  Otherwise, ARRAY[]::foo[] behaves
	 * differently from (ARRAY[])::foo[].
	 */
	while (arg && IsA(arg, A_Expr) &&
		   ((A_Expr *) arg)->kind == AEXPR_PAREN)
		arg = ((A_Expr *) arg)->lexpr;

	/*
	 * If the subject of the typecast is an ARRAY[] construct and the target
	 * type is an array type, we invoke transformArrayExpr() directly so that
	 * we can pass down the type information.  This avoids some cases where
	 * transformArrayExpr() might not infer the correct type.  Otherwise, just
	 * transform the argument normally.
	 */
	if (IsA(arg, A_ArrayExpr))
	{
		Oid			targetBaseType;
		int32		targetBaseTypmod;
		Oid			elementType;

		/*
		 * If target is a domain over array, work with the base array type
		 * here.  Below, we'll cast the array type to the domain.  In the
		 * usual case that the target is not a domain, the remaining steps
		 * will be a no-op.
		 */
		targetBaseTypmod = targetTypmod;
		targetBaseType = getBaseTypeAndTypmod(targetType, &targetBaseTypmod);
		elementType = get_element_type(targetBaseType);
		if (OidIsValid(elementType))
		{
			expr = transformArrayExpr(pstate,
									  (A_ArrayExpr *) arg,
									  targetBaseType,
									  elementType,
									  targetBaseTypmod);
		}
		else
			expr = transformExprRecurse(pstate, arg);
	}
	else
		expr = transformExprRecurse(pstate, arg);

	inputType = exprType(expr);
	if (inputType == InvalidOid)
		return expr;			/* do nothing if NULL input */

	/*
	 * Location of the coercion is preferentially the location of the :: or
	 * CAST symbol, but if there is none then use the location of the type
	 * name (this can happen in TypeName 'string' syntax, for instance).
	 */
	location = tc->location;
	if (location < 0)
		location = tc->typeName->location;

	result = coerce_to_target_type(pstate, expr, inputType,
								   targetType, targetTypmod,
								   COERCION_EXPLICIT,
								   COERCE_EXPLICIT_CAST,
								   location);
	if (result == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_CANNOT_COERCE),
				 errmsg("cannot cast type %s to %s",
						format_type_be(inputType),
						format_type_be(targetType)),
				 parser_coercion_errposition(pstate, location, expr)));

	return result;
}

在这里,需要额外将一个系统表pg_cast,存储数据类型转换路径,包括内建的和用户自定义的类型。用户通过CREATE CAST创建的类型转换也存储在pg_cast系统表中。

  • oid oid : 行标识符
  • castsource oid (references pg_type.oid) : 源数据类型的OID
  • casttarget oid (references pg_type.oid) : 目标数据类型的OID
  • castfunc oid (references pg_proc.oid) : 执行该转换的函数的OID。如果该转换方法不需要一个函数则存储0。
  • castcontext char : 指示该转换能被调用的环境。 e表示仅能作为一个显式转换(使用CAST或::语法)。 a表示在赋值给目标列时隐式调用, 和显式调用一样。 i表示在表达式中隐式调用,和其他转换一样。
  • castmethod char : 指示转换如何被执行。 f表明使用castfunc中指定的函数。 i表明使用输入/输出函数。 b表明该类型是二进制可转换的,因此不需要转换。
sql 复制代码
-- 查一下 int转numeric  pg_cast中存在类型转换, 查pg_proc,转换函数为numeric
postgres@postgres=# select pg_cast.*,pg_proc.proname,pg_proc.pronamespace from pg_cast,pg_proc where pg_cast.castsource=23 and pg_cast.casttarget=1700 and pg_cast.castfunc = pg_proc.oid;
  oid  | castsource | casttarget | castfunc | castcontext | castmethod | proname | pronamespace 
-------+------------+------------+----------+-------------+------------+---------+--------------
 11337 |         23 |       1700 |     1740 | i           | f          | numeric |           11
(1 row)
执行

在逻辑优化阶段,因为复合常量表达式化简,在这个阶段就调用了类型转换函数,返回常量值,到执行器时,仅仅是将常量值返回。

最终会调用int4_numeric转换函数完成类型转换。

c 复制代码
--> pg_plan_queries // 生成执行计划
    --> pg_plan_query
        --> standard_planner
            --> subquery_planner
                --> preprocess_expression  // 预处理targetList 的表达式
                    --> eval_const_expressions  // 常量表达式化简
                        --> simplify_function
                            --> evaluate_function
                                --> evaluate_expr
                                    --> ExecEvalExprSwitchContext
                                        --> ExecInterpExpr
                                            --> int4_numeric // 调用最终的类型转换函数
            --> create_plan
c 复制代码
/* ----------------------------------------------------------------------
 *
 * Type conversion functions
 *
 * ----------------------------------------------------------------------*/
Datum int4_numeric(PG_FUNCTION_ARGS)
{
	int32		val = PG_GETARG_INT32(0);
	Numeric		res;
	NumericVar	result;

	init_var(&result);

	int64_to_numericvar((int64) val, &result);

	res = make_result(&result);

	free_var(&result);

	PG_RETURN_NUMERIC(res);
}

其他的类型转换与之类似,只是部分细节不同。

相关推荐
了一li1 小时前
Qt中的QProcess与Boost.Interprocess:实现多进程编程
服务器·数据库·qt
码农君莫笑1 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
别致的影分身2 小时前
使用C语言连接MySQL
数据库·mysql
京东零售技术3 小时前
“慢”增长时代的企业数据体系建设:超越数据中台
数据库
sdaxue.com4 小时前
帝国CMS:如何去掉帝国CMS登录界面的认证码登录
数据库·github·网站·帝国cms·认证码
o(╥﹏╥)4 小时前
linux(ubuntu )卡死怎么强制重启
linux·数据库·ubuntu·系统安全
阿里嘎多学长5 小时前
docker怎么部署高斯数据库
运维·数据库·docker·容器
Yuan_o_5 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
Sunyanhui15 小时前
牛客网 SQL36查找后排序
数据库·sql·mysql
老王笔记5 小时前
MHA binlog server
数据库·mysql