mysql函数ifnull在pg 9.6中的实现

概述

工作中迁移mysql至pg 9.6,遇到mysql中的ifnull函数在pg中没有,pg中函数coalesce与ifnull功能相同,但函数名不同,需要修改应用。ifnull也在SQL标准中,pg此处不符合sql标准规范。本人尝试修改pg源码添加了ifnull函数,在此做一分享,不当之处请各位批评指正。

ifnull语法规范

语法格式:
IFNULL(expr1 任意类型, expr2 任意类型)

功能:
当expr1为NULL时,用expr2代替本函数式的值;否则本函数的值保持expr1的原值。

参数说明:

expr1数据类型可以是系统的数据类型中的某一个(如:TEXT,INTEGER,FLOAT等)。

expr2数据类型可以是系统的数据类型中的某一个(如:TEXT,INTEGER,FLOAT等)。

expr1和expr2的数据类型应该一致。

返回值说明:
返回值的数据类型:如果expr1不为NULL,数据类型同expr1;如果expr1为NULL, 数据类型同expr2。

postgre实现ifnull方法

pg中没有ifnull函数,但是有功能相同的coalesce函数,在此实现ifnull思路与coalesce相同。coalesce函数在pg中使用形式上是函数,实际上是属于条件表达式(与case、nullif、greatest、least属于同一种类型)。在pg中增加表达式,主要涉及以下部分:

1、词法分析scan.l

sql执行的第1步是词法分析,负责词法分析的代码见src/backend/parser/scan.l,增加ifnull函数,首先需要词法分析程序可以识别ifnull关键字,scan.l中关键字处理相关规则如下:

{identifier}	{
					const ScanKeyword *keyword;
					char	   *ident;

					SET_YYLLOC();

					/* Is it a keyword? */
					keyword = ScanKeywordLookup(yytext,
												yyextra->keywords,
												yyextra->num_keywords);
					if (keyword != NULL)
					{
						yylval->keyword = keyword->name;
						return keyword->value;
					}

					/*
					 * No.  Convert the identifier to lower case, and truncate
					 * if necessary.
					 */
					ident = downcase_truncate_identifier(yytext, yyleng, true);
					yylval->str = ident;
					return IDENT;
				}

此处可以看出函数ScanKeywordLookup(yytext,
yyextra->keywords,
yyextra->num_keywords);
负责查找关键字列表来确定扫描的字符是否关键字,所以此处无需修改scan.l,下一步需要在关键字列表中增中ifnull关键字。

2、在src/include/parser/kwlist.h中增加ifnull关键字

PG_KEYWORD("identity", IDENTITY_P, UNRESERVED_KEYWORD)
PG_KEYWORD("if", IF_P, UNRESERVED_KEYWORD)
PG_KEYWORD("ifnull", IFNULL, COL_NAME_KEYWORD)  //add by jhqin

注意:pg关键字查找使用二分查找法,关键字按字顺排列,我们遵守pg规则,把ifnull关键字加在if关键字后面。

词法解析之后,sql命令处理的第2步是语法分析,接下来增加ifnull表达式的语法处理规则

3、在src/backend/parser/gram.y中增加ifnull表达式处理规则

| IFNULL '(' expr_list ')'   //add by jhqin
                                {
                                        IfnullExpr *c = makeNode(IfnullExpr);
                                        c->args = $3;
                                        c->location = @1;
                                        $$ = (Node *)c;
                                }

4、IfnullExpr结构体声明

gram.y中增加的代码用到了IfnullExpr结构体保存ifnull表达式信息
src/include/nodes/primnodes.h

/*
 *  * IfnullExpr - a IFNULL expression
 *   */
typedef struct IfnullExpr    // add by jhqin
{
        Expr            xpr;
        Oid             ifnulltype;   /* type of expression result */
        Oid             ifnullcollid; /* OID of collation, or InvalidOid if none */
        List       		*args;        /* the arguments */
        int             location;     /* token location, or -1 if unknown */
} IfnullExpr;

5、在nodes.h中增加ifnull节点类型

src/include/nodes/nodes.h

   /*
     * TAGS FOR PRIMITIVE NODES (primnodes.h)
     */
    T_Alias = 300,
    T_RangeVar,
    T_Expr,
    T_Var,
    T_Const,
   。。。//省略中间代码
    T_OnConflictExpr,
    T_IntoClause,
    T_IfnullExpr,       //add by jhqin

6、在解析器代码src/backend/parser/parser_expr.c增加ifnull表达式处理逻辑

 case T_IfnullExpr:   //add by jhqin
                        result = transformIfnullExpr(pstate, (IfnullExpr *) expr);
                        break;

7、上一步用到了transformIfnullExpr函数转换ifnull表达式

函数声明:

static Node *transformIfnullExpr(ParseState *pstate, IfnullExpr *c);   //add by jhqin

函数实现:

static Node *
transformIfnullExpr(ParseState *pstate, IfnullExpr *c)     // add by jhqin
{
        IfnullExpr *newc = makeNode(IfnullExpr);
        List       *newargs = NIL;
        List       *newcoercedargs = NIL;
        ListCell   *args;

        foreach(args, c->args)
        {
                Node       *e = (Node *) lfirst(args);
                Node       *newe;

                newe = transformExprRecurse(pstate, e);
                newargs = lappend(newargs, newe);
        }

        newc->ifnulltype = select_common_type(pstate, newargs, "IFNULL", NULL);
        /* coalescecollid will be set by parse_collate.c */

        /* Convert arguments if necessary */
        foreach(args, newargs)
        {
                Node       *e = (Node *) lfirst(args);
                Node       *newe;

                newe = coerce_to_common_type(pstate, e,
                                                                         newc->ifnulltype,
                                                                         "IFNULL");
                newcoercedargs = lappend(newcoercedargs, newe);
        }

        newc->args = newcoercedargs;
        newc->location = c->location;
        return (Node *) newc;
}

8、在函数exprType中增加对T_IfnullExpr的处理

src/backend/nodes/nodeFuncs.c

case T_IfnullExpr:    //add by jhqin
                        type = ((const IfnullExpr *) expr)->ifnulltype;
                        break;

9、函数expression_tree_walker中增加T_IfnullExpr的处理

src/backend/nodes/nodeFuncs.c

case T_IfnullExpr:  //add by jhqin
                        return walker(((IfnullExpr *) node)->args, context);

10、函数exprSetCollation中增加T_IfnullExpr的处理

src/backend/nodes/nodeFuncs.c

case T_IfnullExpr:  //add by jhqin
                        ((IfnullExpr *) expr)->ifnullcollid = collation;
                        break;

11、函数FigureColnameInternal中增加T_IfnullExpr的处理

src/backend/parse/parse_target.c

case T_IfnullExpr:   //add by jhqin
                        /* make coalesce() act like a regular function */
                        *name = "ifnull";
                        return 2;

12、函数eval_const_expressions_mutator 中增加T_IfnullExpr的处理

src/backend/optimizer/util/clauses.c

case T_IfnullExpr:
                        {
                                IfnullExpr *ifnullexpr = (IfnullExpr *) node;
                                IfnullExpr *newifnull;
                                List       *newargs;
                                ListCell   *arg;

                                newargs = NIL;
                                foreach(arg, ifnullexpr->args)
                                {
                                        Node       *e;

                                        e = eval_const_expressions_mutator((Node *) lfirst(arg),
                                                                                                           context);

                                        /*
                                         * We can remove null constants from the list. For a
                                         * non-null constant, if it has not been preceded by any
                                         * other non-null-constant expressions then it is the
                                         * result. Otherwise, it's the next argument, but we can
                                         * drop following arguments since they will never be
                                         * reached.
                                         */
                                        if (IsA(e, Const))
                                        {
                                                if (((Const *) e)->constisnull)
                                                        continue;       /* drop null constant */
                                                if (newargs == NIL)
                                                        return e;       /* first expr */
                                                newargs = lappend(newargs, e);
                                                break;
                                        }
                                        newargs = lappend(newargs, e);
                                         */
                                        if (IsA(e, Const))
                                        {
                                                if (((Const *) e)->constisnull)
                                                        continue;       /* drop null constant */
                                                if (newargs == NIL)
                                                        return e;       /* first expr */
                                                newargs = lappend(newargs, e);
                                                break;
                                        }
                                        newargs = lappend(newargs, e);
                                }
                                /*
                                 * If all the arguments were constant null, the result is just
                                 * null
                                 */
                                if (newargs == NIL)
                                        return (Node *) makeNullConst(ifnullexpr->ifnulltype,
                                                                                                  -1,
                                                                                           ifnullexpr->ifnullcollid);

                                newifnull = makeNode(IfnullExpr);
                                newifnull->ifnulltype = ifnullexpr->ifnulltype;
                                newifnull->ifnullcollid = ifnullexpr->ifnullcollid;
                                newifnull->args = newargs;
                                newifnull->location = ifnullexpr->location;
                                return (Node *) newifnull;
                        }

13、函数outNode中增加T_IfnullExpr的处理逻辑

src/backend/nodes/outfuncs.c

case T_IfnullExpr:
                                _outIfnullExpr(str, obj);
                                break;

14、增加函数_outIfnullExpr

src/backend/nodes/outfuncs.c

 static void
 _outIfnullExpr(StringInfo str, const IfnullExpr *node)
 {
         WRITE_NODE_TYPE("IFNULL");
 
         WRITE_OID_FIELD(ifnulltype);
         WRITE_OID_FIELD(ifnullcollid);
         WRITE_NODE_FIELD(args);
         WRITE_LOCATION_FIELD(location);
 }

15、重新生成pg源码并重装程序

make clean
make 
make install     

16、启动数据库,测试ifnull函数

postgres=# select ifnull(null,null,'aa');
 ifnull 
--------
 aa
(1 row)

postgres=# select ifnull(null,123);
 ifnull 
--------
    123
(1 row)

postgres=# 

17、完成

你可能感兴趣的:(mysql迁移至pg,postgresql,mysql,迁移学习)