From 7c1953f1cc96f8aba4f74a9ef9e9a49084872e30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=80=E6=8C=83?= <yizhi.fzh@alibaba-inc.com>
Date: Fri, 17 Jul 2020 14:24:52 +0800
Subject: [PATCH v1] Detect the null type correctly in setop case by peeking
 the later

expr. In the past, we will get error when issue select NULL union select NULL
union select 1;  that is mainly because we set the expr type to Text after
handling the first 2 expr with some more reasons. Now before we do that, we
first peak the first not null expr to get the real type we can use, so we can
overcome this issue.
---
 src/backend/parser/analyze.c        | 92 +++++++++++++++++++++++++++++
 src/backend/parser/parse_coerce.c   |  9 ++-
 src/backend/parser/parse_node.c     |  3 +
 src/include/parser/parse_node.h     |  2 +
 src/test/regress/expected/union.out |  8 +++
 src/test/regress/sql/union.sql      |  4 ++
 6 files changed, 116 insertions(+), 2 deletions(-)

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 401da5dedf..a7538b0481 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -80,6 +80,9 @@ static Query *transformCallStmt(ParseState *pstate,
 								CallStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 								   LockingClause *lc, bool pushedDown);
+
+static void preprocessNullTypes(ParseState *pstate, SelectStmt *stmt);
+
 #ifdef RAW_EXPRESSION_COVERAGE_TEST
 static bool test_raw_expression_coverage(Node *node, void *context);
 #endif
@@ -1651,6 +1654,10 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	/*
 	 * Recursively transform the components of the tree.
 	 */
+	pstate->set_targetlist_types = palloc0(list_length(leftmostSelect->targetList)
+										   * sizeof(Oid));
+	preprocessNullTypes(pstate, stmt);
+
 	sostmt = castNode(SetOperationStmt,
 					  transformSetOperationTree(pstate, stmt, true, NULL));
 	Assert(sostmt);
@@ -2012,6 +2019,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		op->colTypmods = NIL;
 		op->colCollations = NIL;
 		op->groupClauses = NIL;
+		pstate->exprpos = 0;
 		forboth(ltl, ltargetlist, rtl, rtargetlist)
 		{
 			TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
@@ -2161,6 +2169,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 										 false);
 				*targetlist = lappend(*targetlist, restle);
 			}
+			pstate->exprpos += 1;
 		}
 
 		return (Node *) op;
@@ -3011,3 +3020,86 @@ test_raw_expression_coverage(Node *node, void *context)
 }
 
 #endif							/* RAW_EXPRESSION_COVERAGE_TEST */
+
+
+
+/*
+ * getSelectTargetList
+ *
+ *	Fill the a list of TargetList in each  query
+ */
+static void
+parseSelectTargetList(SelectStmt *stmt, List **list_ctx)
+{
+	if (stmt->op == SETOP_NONE)
+		*list_ctx = lappend(*list_ctx,  stmt->targetList);
+	else
+	{
+		parseSelectTargetList(stmt->larg, list_ctx);
+		parseSelectTargetList(stmt->rarg, list_ctx);
+	}
+}
+
+
+/*
+ * getProperNullType
+ *
+ * if the expr is not started with nulls, just return 0. or else return first type
+ * of first not null expr.
+ *
+ */
+static Oid
+getProperNullType(ParseState *pstate, List *targetlist_list, int idx)
+{
+	ListCell	*lc;
+	bool previous_is_null;
+	int loops = 0;
+	foreach(lc, targetlist_list)
+	{
+		List *targetlist = lfirst_node(List, lc);
+		Node *node = list_nth_node(ResTarget, targetlist, idx)->val;
+		bool is_null = IsA(node, A_Const)
+			&& nodeTag(&castNode(A_Const, node)->val) == T_Null;
+		if (loops == 0)
+		{
+			if (!is_null)
+				return 0;
+			previous_is_null = true;
+		}
+		else
+		{
+			Assert(previous_is_null);
+			if (!is_null)
+			{
+				Node *expr = transformExpr(pstate, node, EXPR_KIND_SELECT_TARGET);
+				return exprType(expr);
+			}
+			else /* is_null */
+			{
+				/* do nothing */
+			}
+		}
+		loops++;
+	}
+	return 0;
+}
+
+/*
+ * preprocessNullTypes
+ *
+ * detect the select null setop select null [setop] select expr; case.
+ * set the type oid to be same as expr's oid.
+ */
+static void
+preprocessNullTypes(ParseState *pstate, SelectStmt *stmt)
+{
+	List	*targetlist_list =  NIL;
+	int len, idx;
+
+	parseSelectTargetList(stmt, &targetlist_list);
+	len = list_length(linitial(targetlist_list));
+	for(idx = 0;  idx < len; idx++)
+	{
+		pstate->set_targetlist_types[idx] = getProperNullType(pstate, targetlist_list, idx);
+	}
+}
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 1b11cf731c..437c6e32f6 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1384,8 +1384,13 @@ select_common_type(ParseState *pstate, List *exprs, const char *context,
 	 * now.
 	 */
 	if (ptype == UNKNOWNOID)
-		ptype = TEXTOID;
-
+	{
+		if (pstate->set_targetlist_types != NULL &&
+			pstate->set_targetlist_types[pstate->exprpos] != 0)
+			ptype = pstate->set_targetlist_types[pstate->exprpos];
+		else
+			ptype = TEXTOID;
+	}
 	if (which_expr)
 		*which_expr = pexpr;
 	return ptype;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 6e98fe55fc..2f29f4241b 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -65,6 +65,9 @@ make_parsestate(ParseState *parentParseState)
 		pstate->p_queryEnv = parentParseState->p_queryEnv;
 	}
 
+	pstate->exprpos = 0;
+	pstate->set_targetlist_types = NULL;
+
 	return pstate;
 }
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..e2cfe89f0c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -220,6 +220,8 @@ struct ParseState
 	ParseParamRefHook p_paramref_hook;
 	CoerceParamHook p_coerce_param_hook;
 	void	   *p_ref_hook_state;	/* common passthrough link for above */
+	int		exprpos;
+	Oid 	*set_targetlist_types;
 };
 
 /*
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 6e72e92d80..b73653b10d 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -1052,3 +1052,11 @@ where (x = 0) or (q1 >= q2 and q1 <= q2);
  4567890123456789 |  4567890123456789 | 1
 (6 rows)
 
+-- Test we should be able to detect the null types correctly.
+select null union all select null union select 1;
+ ?column? 
+----------
+         
+        1
+(2 rows)
+
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 5f4881d594..e7fc74d8e8 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -440,3 +440,7 @@ select * from
    union all
    select *, 1 as x from int8_tbl b) ss
 where (x = 0) or (q1 >= q2 and q1 <= q2);
+
+
+-- Test we should be able to detect the null types correctly.
+select null union all select null union select 1;
-- 
2.21.0

