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);
}

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

相关推荐
pp-周子晗(努力赶上课程进度版)几秒前
【MySQL】视图、用户管理、MySQL使用C\C++连接
数据库·mysql
斯特凡今天也很帅9 分钟前
clickhouse常用语句汇总——持续更新中
数据库·sql·clickhouse
一加一等于二1 小时前
docker部署postgresql17,并且安装插件
docker·postgresql
超级小忍1 小时前
如何配置 MySQL 允许远程连接
数据库·mysql·adb
吹牛不交税2 小时前
sqlsugar WhereIF条件的大于等于和等于查出来的坑
数据库·mysql
hshpy2 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
文牧之3 小时前
Oracle 审计参数:AUDIT_TRAIL 和 AUDIT_SYS_OPERATIONS
运维·数据库·oracle
篱笆院的狗3 小时前
如何使用 Redis 快速实现布隆过滤器?
数据库·redis·缓存
洛神灬殇4 小时前
【LLM大模型技术专题】「入门到精通系列教程」基于ai-openai-spring-boot-starter集成开发实战指南
网络·数据库·微服务·云原生·架构
小鸡脚来咯4 小时前
redis分片集群架构
数据库·redis·架构