暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

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

原创 chirpyli 2023-01-11
2232

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

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

源码分析

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

CREATE CAST (source_type AS target_type) WITH FUNCTION function_name [ (argument_type [, ...]) ] [ AS ASSIGNMENT | AS IMPLICIT ]

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

主流程
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的定义:

/* 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_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节点:

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; }
语义分析阶段

主流程:

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

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

/* * 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表明该类型是二进制可转换的,因此不需要转换。
-- 查一下 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转换函数完成类型转换。

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

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

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论