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、完成

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章