openGauss学习—— parse_param.cpp 解析

引言

在前两篇博客中,我对parser目录下的 parse_oper.cpp 文件的部分内容做了解析。本篇博客对同目录下与查询解析中参数处理相关的文件 parse_param.cpp 的部分内容进行学习和解读。

文件路径

src/common/backend/parser/parse_param.cpp

主要结构体

文件内定义了两个结构体 FixedParamState和VarParamState。定义细节如下:

/* 固定的参数声明 */
typedef struct FixedParamState {    
    Oid* paramTypes; /* array of parameter type OIDs */
    int numParams;   /* number of array entries */
} FixedParamState;
/* 可变的参数声明 */
typedef struct VarParamState {
    Oid** paramTypes; /* array of parameter type OIDs */
    int* numParams;   /* number of array entries */
#ifndef ENABLE_MULTIPLE_NODES    
    char **paramTypeNames;
#endif    
} VarParamState;

结构FixedParamState和VarParamState分别代表固定(fixed)和可变(variable)两种结构参数的声明。对于固定参数FixedParamState类型,其只有两个成员变量,分别是存储参数类型OID的OID指针paramTypes和参数的条目数量numParams。而对于可变参数VarParamState,其还拥有一个char ** 类型的成员,包括在#ifndef宏中。在本文件中并未使用到这一成员,因此我们先不对它过多关注。

另外值得注意的是,FixedParamState中的paramTypes成员是Oid类型的一级指针,而在VarParamState类型中这一成员为Oid的二级指针。这是因为对于VarParamState类型,parameter的数目是可变的,那么就可能出现所需的内存空间超出原先分配空间大小的情况。将paramTypes设置为二级指针,可以使得调用者在不改变paramTypes的值的情况下,为参数类型OID的存储重新分配空间。

函数解析

void parse_fixed_parameters(ParseState* pstate, Oid* paramTypes, int numParams)

函数功能是将包含查询的引用references转化为固定参数结构FixedParamState,即输入查询引用的参数,为其构造FixedParamState结构。

void parse_fixed_parameters(ParseState* pstate, Oid* paramTypes, int numParams)
{
    // palloc for FixedParamState
    FixedParamState* parstate = (FixedParamState*)palloc(sizeof(FixedParamState));
    // 构造FixedParamState结构 
    parstate->paramTypes = paramTypes;
    parstate->numParams = numParams;
    // 查询语句语义分析相关
    pstate->p_ref_hook_state = (void*)parstate;
    pstate->p_paramref_hook = fixed_paramref_hook;
    /* no need to use p_coerce_param_hook */
}
void parse_variable_parameters(ParseState* pstate, Oid** paramTypes, int* numParams)

函数功能是将包含查询的引用references转化为可变参数结构VarParamState,即输入查询引用的参数,为其构造VarParamState结构。

void parse_variable_parameters(ParseState* pstate, Oid** paramTypes, int* numParams)
{
    // palloc for VarParamState
    VarParamState* parstate = (VarParamState*)palloc(sizeof(VarParamState));
    // 构造VarParamState结构 
    parstate->paramTypes = paramTypes;
    parstate->numParams = numParams;
    // 查询语句语义分析相关
    pstate->p_ref_hook_state = (void*)parstate;
    pstate->p_paramref_hook = variable_paramref_hook;
    pstate->p_coerce_param_hook = variable_coerce_param_hook;
}
static Node* fixed_paramref_hook(ParseState* pstate, ParamRef* pref)
static Node* variable_paramref_hook(ParseState* pstate, ParamRef* pref)

这两个函数的作用是将参数引用ParamRef结构类型的数据转化为FixedParamState和VarParamState结构并返回。ParamRef的结构定义如下:

/*
 * 文件路径为/src/include/nodes/parsenodes_common.h
 * ParamRef - specifies a $n parameter reference
 */
typedef struct ParamRef {
    NodeTag type;
    int number;   /* 参数的数目 */
    int location;   /* token的位置,若为unknown此项为-1。*/
} ParamRef;
static Node* variable_coerce_param_hook(ParseState* pstate, Param* param, Oid targetTypeId, int32 targetTypeMod, int location)

函数的功能是将输入的参数param强制转换为查询请求所需的格式类型,且参数应当是variable的。

void check_variable_parameters(ParseState* pstate, Query* query)

函数用于检查parse_variable_parameters函数解析完成后可变参数的赋值是否满足一致性。注意:函数没有对参数被使用的所有地方进行检查,这是有意而为之,并且也不保证参数都是非UNKNOWN指派的。若这些检查是重要的,调用者应当强制进行这些检查。

void check_variable_parameters(ParseState* pstate, Query* query)
{
    VarParamState* parstate = (VarParamState*)pstate->p_ref_hook_state;
    // 如果参数条目数量为0则没有生成任何Params结构,无需进行检查 
    if (*parstate->numParams > 0) {
        (void)query_tree_walker(query, (bool (*)())check_parameter_resolution_walker, (void*)pstate, 0);
    }
}

具体的check逻辑并不在函数中实现,函数只负责完成检查函数的调用。注意到函数通过调用query_tree_walker对语法树进行检查以确保参数的赋值是满足一致性的,并且传入了指向check_parameter_resolution_walker函数的函数指针。另外,如果numParams = 0,函数不进行检查工作,因为此时参数条目数量为0,无需进行检查;另外当参数类型UNKNOWN时是仍进行检查的(这是区别于numParams=0的另一种情况)。

static bool check_parameter_resolution_walker(Node* node, ParseState* pstate)

函数功能是对完整解析后的语法树进行遍历以验证参数符号与其类型是匹配的。

/*
 * Traverse a fully-analyzed tree to verify that parameter symbols
 * match their types.  We need this because some Params might still
 * be UNKNOWN, if there wasn't anything to force their coercion,
 * and yet other instances seen later might have gotten coerced.
 * 对完整解析后的语法树进行遍历以验证参数符号与其类型是匹配的。 
 * 这个过程是必要的,因为如果没有其他的进程对参数进行类型转换
 * 或还未来得及进行类型转换,那么此时参数的类型仍可能是UNKNOWN的。 
 */
static bool check_parameter_resolution_walker(Node* node, ParseState* pstate)
{
    if (node == NULL) {
        return false;
    }
    if (IsA(node, Param)) {    // 若当前结点是Param类的对象 
        Param* param = (Param*)node;    // 保证转换的安全性后进行转换 
        if (param->paramkind == PARAM_EXTERN) {    // 如果是外部参数 
            VarParamState* parstate = (VarParamState*)pstate->p_ref_hook_state;
            int paramno = param->paramid;
            if (paramno <= 0 || /* shouldn't happen, but... */
                paramno > *parstate->numParams) {
                ereport(ERROR,
                    (errcode(ERRCODE_UNDEFINED_PARAMETER),
                        errmsg("there is no parameter $%d", paramno),
                        parser_errposition(pstate, param->location)));
            }
            if (param->paramtype != (*parstate->paramTypes)[paramno - 1]) {
                ereport(ERROR,
                    (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
                        errmsg("could not determine data type of parameter $%d", paramno),
                        parser_errposition(pstate, param->location)));
            }
        }
        return false;
    }
    if (IsA(node, Query)) {    // 当前结点是Query类的对象 
        // 递归到RTE子查询或尚未计划的子链路子查询 
        return query_tree_walker((Query*)node, (bool (*)())check_parameter_resolution_walker, (void*)pstate, 0);
    }
    return expression_tree_walker(node, (bool (*)())check_parameter_resolution_walker, (void*)pstate);
}

代码主体通过if语句分成四个逻辑分支:若当前节点node==NULL,返回false表示验证失败;否则利用IsA函数通过对node类型的区分进入不同的代码块中。

  • 若IsA(node, Param)==true,即结点是Param类型的对象,return false表示验证失败。另外当参数类别为PARAM_EXTERN还会进行错误判断,生成相应的错误信息并可能raise error;
  • 若IsA(node, Query)==true,即结点是Query类型的对象,调用query_tree_walker函数进行递归验证查询树,返回验证结果;
  • 若上述都不满足,则调用expression_tree_walker函数对表达式树进行递归调用检查,返回检查结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值