openGauss学习—— parse_oper.cpp 解析

引言

本篇主要对SQL语法解析相关文件parse_oper.cpp的内容进行解析。

文件路径

src/common/backend/parser/parse_oper.cpp

主要结构体声明

文件首先定义了两个在操作符处理过程中会用到的结构体OprCacheEntry和OprCacheKey。

typedef struct OprCacheKey {
    char oprname[NAMEDATALEN];    // operator名称 
    Oid left_arg;  /* Left input OID, or 0 if prefix op */
                   // 左侧操作符的OID,如果这是一个前缀操作符则此项为0. 
    Oid right_arg; /* Right input OID, or 0 if postfix op */
                   // 右侧操作符的OID,如果这是一个后缀操作符则此项为0. 
    Oid search_path[MAX_CACHED_PATH_LEN]; // 搜索路径 
    bool use_a_style_coercion;
} OprCacheKey;
typedef struct OprCacheEntry {
    /* the hash lookup key MUST BE FIRST */
    OprCacheKey key;  // 哈希查找值必须是第一项 
     /* OID of the resolved operator */
    Oid opr_oid;       // 解决后的OID 
} OprCacheEntry;

它们的包含关系和数据成员概括如下。

首先来看OprCacheKey。它有五个数据成员,分别是操作符的名称oprname,左右操作符的OID值,搜索路径(数据类型为一个OID数组)以及一个bool类型变量use_a_style_coercion。对于各个成员变量所司职责我们目前还不能准确知晓,在对文件中其他部分内容进行解析后也许会有更进一步的理解。

另外显然结构类型OID是Operator ID的意思,即每个操作符与其唯一的OID值相对应。这也体现在OprCacheEnrty的结构中:每一个OprCacheKey值唯一地对应一个OID值opr_oid。

LookupOperName()

函数源代码及注释如下:

/*
 * LookupOperName
 *        Given a possibly-qualified operator name and exact input datatypes,
 *        look up the operator.
 *        给定一个可能存在的操作符名称以及确切的字节信息,查找对应操作符的OID 
 *
 * Pass oprleft = InvalidOid for a prefix op, oprright = InvalidOid for
 * a postfix op.
 * 当为一个前缀操作符时,入口参数oprleft = InvalidOid
 * 当为一个后缀操作符时,入口参数oprright = InvalidOid 
 *
 * If the operator is not found, we return InvalidOid if noError is true,
 * else raise an error.  pstate and location are used only to report the
 * error position; pass NULL/-1 if not available.
 * 如果查找失败,且noError的值为true,返回InvalidOid,否则报出异常。
 * 入口参数pstate和location仅用于向调用者报告异常发生的位置。 
 */
Oid LookupOperName(ParseState* pstate, List* opername, Oid oprleft, Oid oprright, bool noError, int location)
{
    Oid result;
    // 根据OprCacheKey.left_arg.oprname,OprCacheKey.left_arg, OprCacheKey.right_arg
    // 的值查找OID,保存在result中;查找成功则返回result。 
    result = OpernameGetOprid(opername, oprleft, oprright);
    if (OidIsValid(result))
        return result;
    /* we don't use op_error here because only an exact match is wanted */
    // OpernameGetOprid查找失败,即result = InValid,并且输入信号noError = false
    // 这表示调用者允许将查找失败的情况当作error处理。此if块用于报告错误信息。 
    if (!noError) {
        char oprkind;
        if (!OidIsValid(oprleft))
            oprkind = 'l';
        else if (!OidIsValid(oprright))
            oprkind = 'r';
        else
            oprkind = 'b';
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_FUNCTION),
                errmsg("operator does not exist: %s", op_signature_string(opername, oprkind, oprleft, oprright)),
                parser_errposition(pstate, location)));
    }
    return InvalidOid;
}

函数功能:给定一个可能存在的操作符名称以及确切的字段信息,查找其对应的OID。

入口参数:操作符名称opername,操作符字段中left_arg和right_arg的值,用以指定查找失败时是否抛出error的bool变量noError,以及与确定错误信息相关的变量。

出口参数:查找结果。若查找成功返回对应的OID,否则返回InvalidOid。

函数的内部逻辑是十分简单的,通过调用OpernameGetOprid函数,传入操作符名称opername、左右操作符OID oprleft、oprright进行操作符OID的查找。如果查找成功,返回得到的OID值;否则返回InvalidOid。至于具体的查找逻辑不在本函数中实现,函数只负责调用查找函数并将查找结果返回给上级。

另外,函数通过指定入口参数noError来选择当查找OID失败时是否要报出错误信息。当noError = false时,如果操作符对应的OID查找失败,则函数会负责错误位置的定位和错误信息的生成,并将错误报出。

LookupOperNameTypeNames()

函数源码及注释如下:

/*
 * LookupOperNameTypeNames
 *        Like LookupOperName, but the argument types are specified by
 *        TypeName nodes.
 *      与函数LookupOperName相似,但当传入oprleft,oprright的类型
 *        为TypeName*时调用本函数。
 */
Oid LookupOperNameTypeNames(
    ParseState* pstate, List* opername, TypeName* oprleft, TypeName* oprright, bool noError, int location)
{
    Oid leftoid, rightoid;
    if (oprleft == NULL)
        leftoid = InvalidOid;
    else
        leftoid = typenameTypeId(pstate, oprleft);
    if (oprright == NULL)
        rightoid = InvalidOid;
    else
        rightoid = typenameTypeId(pstate, oprright);
    return LookupOperName(pstate, opername, leftoid, rightoid, noError, location);
}

函数的功能与LookupOperName函数类似,即给定操作符的名称opername以及相应的字段信息,对其对应的OID进行查找。不同的是,本函数传入的oprleft,oprright的数据类型是TypeName*。函数先通过调用typenameTypeId函数完成oprleft和oprright对应的OID信息的查找,获取其对应的OID后调用LookupOperName函数,获取操作符对应的OID信息。

函数的入口参数、出口参数除oprleft、oprright数据类型不同外其他均与LookupOperName函数相同。事实上函数是对LookupOperName函数的扩展。函数内部并不实现具体的查找逻辑,只负责函数的调用,并将查找结果返回给上级。另外,由于本函数调用了LookupOperName函数,因此也有可能raise error(即noError=false且查找失败的情况)。

可哈希hashable

先来补充一个概念——hashable可哈希的。通常来讲,一个对象在其生命周期内,如果保持不变,就是hashable(可哈希的)。可哈希的数据类型,通常为不可变的数据结构,如字符串、对象集等等;同理,可变的数据结构如数组(列表)、集合等,就是不可哈希的(unhashable)。

哈希是一个将大体量数据映射为很小的数据的过程,以便我们可以在固定的时间复杂度下进行对应数据的查询(很多时候时间复杂度仅为O(1))。因此,哈希以及可哈希的概念对高效的算法和数据结构是十分重要的。

函数get_sort_group_operators()

函数源码及注释如下:

/*
 * get_sort_group_operators - get default sorting/grouping operators for type
 * 为指定类型获取其与比较和分组相关的操作符 
 *
 * We fetch the "<", "=", and ">" operators all at once to reduce lookup
 * overhead (knowing that most callers will be interested in at least two).
 * However, a given datatype might have only an "=" operator, if it is
 * hashable but not sortable.  (Other combinations of present and missing
 * operators shouldn't happen, unless the system catalogs are messed up.)
 * 我们一次完成对 "<", "=", ">"的查找以避免反复调用造成的开销(因为大多数
 * 时候我们都需要知悉以上至少两种的操作符)。尽管如此,一种给定的数据类型
 * 是可能只支持 "=" 这一个比较操作的,如果他是不可比较但是可哈希的。 
 *
 * If an operator is missing and the corresponding needXX flag is true,
 * throw a standard error message, else return InvalidOid.
 * 如果以上的一种操作符是缺失的,但传入的信号neadXX指定了这个操作符
 * 是必须的,那么会抛出一个标准错误信息;否则返回InvalidOid。 
 *
 * In addition to the operator OIDs themselves, this function can identify
 * whether the "=" operator is hashable.
 * 作为对每种类型对应比较操作符的OID的补充,函数还可以识别 "=" 是否是可哈希的。 
 *
 * Callers can pass NULL pointers for any results they don't care to get.
 * 如果某个比较符的OID是不需要的,调用者可以对相应的参数传入NULL。 
 *
 * Note: the results are guaranteed to be exact or binary-compatible matches,
 * since most callers are not prepared to cope with adding any run-time type
 * coercion steps.
 * 注意:结果是保证精确或二进制兼容的匹配,因为大多数调用者还没有准备好应对
 * 添加任何运行时类型强制措施。
 */
void get_sort_group_operators(
    Oid argtype, bool needLT, bool needEQ, bool needGT, Oid* ltOpr, Oid* eqOpr, Oid* gtOpr, bool* isHashable)
{
    TypeCacheEntry* typentry = NULL;
    int cache_flags;
    Oid lt_opr;    // 操作符对应的 "<" 的OID 
    Oid eq_opr;    // "=" 的OID 
    Oid gt_opr;    // ">" 的OID 
    bool hashable = false;
    /*
     * Look up the operators using the type cache.
     * 利用数据类型cache进行符号的查找 
     *
     * Note: the search algorithm used by typcache.c ensures that the results
     * are consistent, ie all from matching opclasses.
     */
    if (isHashable != NULL)
        cache_flags = TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR | TYPECACHE_HASH_PROC;
    else
        cache_flags = TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR | TYPECACHE_GT_OPR;
    /* 通过传入的argtype获取type对应的TypeCacheEntry */
    typentry = lookup_type_cache(argtype, cache_flags);
    lt_opr = typentry->lt_opr;    // <
    eq_opr = typentry->eq_opr;    // =
    gt_opr = typentry->gt_opr;    // >
    hashable = OidIsValid(typentry->hash_proc);
    /* Report errors if needed */
    // 可选的报错信息 
    if ((needLT && !OidIsValid(lt_opr)) || (needGT && !OidIsValid(gt_opr)))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_FUNCTION),
                errmsg("could not identify an ordering operator for type %s", format_type_be(argtype)),
                errhint("Use an explicit ordering operator or modify the query.")));
    if (needEQ && !OidIsValid(eq_opr))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_FUNCTION),
                errmsg("could not identify an equality operator for type %s", format_type_be(argtype))));
    /* Return results as needed */
    // 将结果保存在指针中返回 
    if (ltOpr != NULL)
        *ltOpr = lt_opr;
    if (eqOpr != NULL)
        *eqOpr = eq_opr;
    if (gtOpr != NULL)
        *gtOpr = gt_opr;
    if (isHashable != NULL)
        *isHashable = hashable;
}

函数功能:为指定的类型Type获取其相关的用于比较/分组的操作符信息(<,=,>)。

入口参数:操作符类型OID argtype以及指定三个比较符是否必须的bool信号needGT,needEQ,needLT。

出口参数:指定类型下<、=、>的OID信息,以及可哈希布尔值hashable(均以指针方式传递)。

对于不同的操作符,其对应的比较符号<,=,>的OID是不同的,并非是相同的OID。函数传入某操作符的类型,通过类型cache查找上述三个比较符的OID。查找得到的结果储存在如下数据结构中:

/* --- typcache.h --- */
typedef struct TypeCacheEntry {
    /* typeId is the hash lookup key and MUST BE FIRST */
    Oid type_id; /* OID of the data type */
    int16 typlen;
       //……
    Oid eq_opr;         /* the equality operator */
    Oid lt_opr;         /* the less-than operator */
    Oid gt_opr;         /* the greater-than operator */
//……
} TypeCacheEntry;

可以看到结构TypeCacheEntry包含了十余个字段信息(部分省略),其中包括操作符类型type_id,类型的字节长度,以及其对应的 >,<,= 三种比较符的OID值。

通过调用lookup_type_cache函数进行cache查找后,访问TypeCacheEntry对应的字段信息就得到了三个操作符对应的OID。此外,TypeCacheEntry中还包括hash_proc字段,这表示该操作符是否是hashable的。

在得到三个比较符号的OID及hashable信息后,要根据调用者输入的neadXX信号来进行是否抛出error的选择。例如,指定neadGT = true,即大于号”>”的OID是必需的,若此时从TypeCacheEntry结构中读取到的gt_opr字段为InValid,则要生成对应的错误信息,并且raise error。

最终,函数将查找的三个比较符的OID信息及hashable信息存放在调用者指定的存储空间中,返回调用处(此情况为error不发生的前提下)。

make_op()

函数源码及注释(部分源代码省略):

/*
 * make_op
 *        Operator expression construction.
 *        为操作符operator构造expression结构。 
 *
 * Transform operator expression ensuring type compatibility.
 * This is where some type conversion happens.
 * 操作符expression的转换要确保类型兼容,在这里进行一些类型转换。 
 */
Expr* make_op(ParseState* pstate, List* opname, Node* ltree, Node* rtree, int location, bool inNumeric)
{
    /* 输入操作符名称及左右操作符的结构树,为其构造expression结构 */
    Oid ltypeId, rtypeId;     // 左右操作符的OID 
    Operator tup;             // 构造OpExpr之前需先获得操作符对应的Operator结构 
    Form_pg_operator opform; // 与Pg_Operator和Operator的类型转换相关 
    Oid actual_arg_types[2]; // 由ltree,rtree决定的实际左右操作符的OID 
    Oid declared_arg_types[2];  // 在Pg_Operator结构下的OID 
    int nargs;                 // 左右操作符的数目(0,1,2)。 
    List* args = NIL;         // 代表左右操作符的树结构 
    Oid rettype;             // 返回操作符类型的OID 
    OpExpr* result = NULL;     // 返回的expression结构 
    // 通过exprType函数计算左右操作符的OID, 
    // 再利用oper(),right_oper(),left_oper()函数获取操作符对应的Operator结构 
    if (rtree == NULL) {
        /* right operator */ 
        ltypeId = exprType(ltree);
        rtypeId = InvalidOid;
        tup = right_oper(pstate, opname, ltypeId, false, location);
    } else if (ltree == NULL) {
        /* left operator */
        rtypeId = exprType(rtree);
        ltypeId = InvalidOid;
        tup = left_oper(pstate, opname, rtypeId, false, location);
    } else {
        /* otherwise, binary operator */
        ltypeId = exprType(ltree);
        rtypeId = exprType(rtree);
        ……
        tup = oper(pstate, opname, ltypeId, rtypeId, false, location, inNumeric);
    }
    // 与Pg_Operator与Operator之间的类型转换有关。opform是一个Form_pg_operator类型的结构体变量 
    opform = check_operator_is_shell(opname, pstate, location, tup);
    /* Do typecasting and build the expression tree */
    // 进行类型转换,构造表达式树结构 
    if (rtree == NULL) {
        /* right operator */
        args = list_make1(ltree);
        actual_arg_types[0] = ltypeId;    // 实际的OID,由ltree决定的ltypeId 
        declared_arg_types[0] = opform->oprleft; // PG中的OID 
        nargs = 1;        
    } else if (ltree == NULL) {
        /* left operator */
        args = list_make1(rtree);    
        actual_arg_types[0] = rtypeId;    // 实际的OID,由rtree决定的rtypeId 
        declared_arg_types[0] = opform->oprright; // PG中的OID 
        nargs = 1;
    } else {
        /* otherwise, binary operator */
        args = list_make2(ltree, rtree);
        actual_arg_types[0] = ltypeId;
        actual_arg_types[1] = rtypeId;
        declared_arg_types[0] = opform->oprleft;
        declared_arg_types[1] = opform->oprright;
        nargs = 2;    // 左右操作符均是valid的
    }
    ……
    /* perform the necessary typecasting of arguments */
    // 为局部变量进行必要的类型转换 
    make_fn_arguments(pstate, args, actual_arg_types, declared_arg_types);
    /* and build the expression node */
    // 完善返回的expression结构的内容 
    result = makeNode(OpExpr);
    result->opno = oprid(tup);    // 操作符类型OID 
    result->opfuncid = opform->oprcode;    // PG中的OID 
    ……
    /* opcollid and inputcollid will be set by parse_collate.c */
    result->args = args;    // 左右操作符的树结构 
    result->location = location;    
    ReleaseSysCache(tup);    // 释放内存空间 
    return (Expr*)result;    // 转化为Expr*类型返回 
}

函数功能:输入操作符名称及左右操作符的语法树结构,为其创建一个OpExpr结构并返回给调用者。

入口参数:操作符名、左右操作符的语法树ltree,rtree、与查询语句语义解析相关的指针pstate等。

出口参数:构造的OpExpr结构,强制类型转换为Expr* 后返回给调用者。

函数为操作符构造OpExpr结构,首先需要确定其Operator具体结构。输入参数中包括左右操作符的语法树ltree和rtree,函数先通过调用exprType函数得到语法树对应的OID的值,即ltypeId和rtypeId;再通过ltypeId和rtypeId的值以及操作符名称opname对操作符对应的Operator结构进行cache查找。

查找过程是通过调用函数oper、left_oper和right_oper来实现的,本函数内部并不实现具体的查找逻辑。函数oper、left_oper和right_oper同样在parse_oper.cpp中,它们的作用是给定操作符名opername以及左右操作符的OID值,为操作符查找其对应的Operator结构信息(分别对应左右操作符语法树都存在,右操作符InValid和左操作符InValid三种情况)。

这样就得到了操作符对应的Operator结构。接下来通过调用check_operator_is_shell函数得到一个Form_pg_operator类型的结构体变量 opform,它是与pg_operator和Operator之间的类型转换相关的。

接下来为操作符构建树形的语法结构。具体操作是,通过函数list_make1和list_make2(分别对应左右操作符有1或2个有效的情况)获取左右操作符语法树的List* 结构,存放在变量 List* arg中。并利用数组actual_arg_types来保存在之前的过程中计算得到的ltypeId和rtypeId的值,declared_arg_types保存opform字段oprleft和oprright的值,再调用make_fn_arguments函数,为actual_arg_types和declared_arg_types做必要的类型转换。

接下来调用makeNode函数,指定node类型为OpExpr,就得到了一个初始的OpExpr结点,令OpExpr类型指针result指向它。然后通过result来访问结点的各个数据域,为其赋值,如操作符OID、语法树列表指针等。完成上述操作后,释放cache空间,并将result强制转换成Expr* 类型(由命名推测为OpExpr父类)后返回给上级。

总结

在本篇博客中,我进行了对SQL语法分析中操作符处理相关内容的探索,并对 parse_oper.cpp 文件中的部分函数进行了学习和注解工作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值