工作中迁移mysql至pg 9.6,遇到mysql中的ifnull函数在pg中没有,pg中函数coalesce与ifnull功能相同,但函数名不同,需要修改应用。ifnull也在SQL标准中,pg此处不符合sql标准规范。本人尝试修改pg源码添加了ifnull函数,在此做一分享,不当之处请各位批评指正。
语法格式:
IFNULL(expr1 任意类型, expr2 任意类型)
功能:
当expr1为NULL时,用expr2代替本函数式的值;否则本函数的值保持expr1的原值。
参数说明:
expr1数据类型可以是系统的数据类型中的某一个(如:TEXT,INTEGER,FLOAT等)。
expr2数据类型可以是系统的数据类型中的某一个(如:TEXT,INTEGER,FLOAT等)。
expr1和expr2的数据类型应该一致。
返回值说明:
返回值的数据类型:如果expr1不为NULL,数据类型同expr1;如果expr1为NULL, 数据类型同expr2。
pg中没有ifnull函数,但是有功能相同的coalesce函数,在此实现ifnull思路与coalesce相同。coalesce函数在pg中使用形式上是函数,实际上是属于条件表达式(与case、nullif、greatest、least属于同一种类型)。在pg中增加表达式,主要涉及以下部分:
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关键字。
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表达式的语法处理规则
| IFNULL '(' expr_list ')' //add by jhqin
{
IfnullExpr *c = makeNode(IfnullExpr);
c->args = $3;
c->location = @1;
$$ = (Node *)c;
}
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;
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
case T_IfnullExpr: //add by jhqin
result = transformIfnullExpr(pstate, (IfnullExpr *) expr);
break;
函数声明:
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;
}
src/backend/nodes/nodeFuncs.c
case T_IfnullExpr: //add by jhqin
type = ((const IfnullExpr *) expr)->ifnulltype;
break;
src/backend/nodes/nodeFuncs.c
case T_IfnullExpr: //add by jhqin
return walker(((IfnullExpr *) node)->args, context);
src/backend/nodes/nodeFuncs.c
case T_IfnullExpr: //add by jhqin
((IfnullExpr *) expr)->ifnullcollid = collation;
break;
src/backend/parse/parse_target.c
case T_IfnullExpr: //add by jhqin
/* make coalesce() act like a regular function */
*name = "ifnull";
return 2;
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;
}
src/backend/nodes/outfuncs.c
case T_IfnullExpr:
_outIfnullExpr(str, obj);
break;
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);
}
make clean
make
make install
postgres=# select ifnull(null,null,'aa');
ifnull
--------
aa
(1 row)
postgres=# select ifnull(null,123);
ifnull
--------
123
(1 row)
postgres=#