From 762f8a0ec276a64edda3d6f57d67be1ef7419b8f Mon Sep 17 00:00:00 2001
From: Alena Rybakina <a.rybakina@postgrespro.ru>
Date: Thu, 20 Feb 2025 23:12:04 +0300
Subject: [PATCH 1/2] Move the function for generating ArrayExpr to another
 place so that it can be used by other optimizations.

Since const and expr types may be different from the column type,
we must take this into account and therefore must form a suitable operator
for the input types and convert left expr to the target output type.
We do this to avoid a situation where the incoming operator for Array
expression cannot be applied to the given types.

clauses.c file was chosen due to the fact that only two additional libraries
needed to be added compared to other places.
---
 src/backend/optimizer/path/indxpath.c |  63 +--------
 src/backend/optimizer/util/clauses.c  | 176 ++++++++++++++++++++++++++
 src/include/optimizer/optimizer.h     |   2 +
 3 files changed, 181 insertions(+), 60 deletions(-)

diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index a43ca16d683..e8910c6caf4 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -33,10 +33,8 @@
 #include "optimizer/paths.h"
 #include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
-#include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
-#include "utils/syscache.h"
 
 
 /* XXX see PartCollMatchesExprColl */
@@ -3247,7 +3245,6 @@ match_orclause_to_indexcol(PlannerInfo *root,
 	BoolExpr   *orclause = (BoolExpr *) rinfo->orclause;
 	Node	   *indexExpr = NULL;
 	List	   *consts = NIL;
-	Node	   *arrayNode = NULL;
 	ScalarArrayOpExpr *saopexpr = NULL;
 	Oid			matchOpno = InvalidOid;
 	IndexClause *iclause;
@@ -3418,63 +3415,9 @@ match_orclause_to_indexcol(PlannerInfo *root,
 		return NULL;
 	}
 
-	/*
-	 * Assemble an array from the list of constants.  It seems more profitable
-	 * to build a const array.  But in the presence of other nodes, we don't
-	 * have a specific value here and must employ an ArrayExpr instead.
-	 */
-	if (haveNonConst)
-	{
-		ArrayExpr  *arrayExpr = makeNode(ArrayExpr);
-
-		/* array_collid will be set by parse_collate.c */
-		arrayExpr->element_typeid = consttype;
-		arrayExpr->array_typeid = arraytype;
-		arrayExpr->multidims = false;
-		arrayExpr->elements = consts;
-		arrayExpr->location = -1;
-
-		arrayNode = (Node *) arrayExpr;
-	}
-	else
-	{
-		int16		typlen;
-		bool		typbyval;
-		char		typalign;
-		Datum	   *elems;
-		int			i = 0;
-		ArrayType  *arrayConst;
-
-		get_typlenbyvalalign(consttype, &typlen, &typbyval, &typalign);
-
-		elems = (Datum *) palloc(sizeof(Datum) * list_length(consts));
-		foreach_node(Const, value, consts)
-		{
-			Assert(!value->constisnull);
-
-			elems[i++] = value->constvalue;
-		}
-
-		arrayConst = construct_array(elems, i, consttype,
-									 typlen, typbyval, typalign);
-		arrayNode = (Node *) makeConst(arraytype, -1, inputcollid,
-									   -1, PointerGetDatum(arrayConst),
-									   false, false);
-
-		pfree(elems);
-		list_free(consts);
-	}
-
-	/* Build the SAOP expression node */
-	saopexpr = makeNode(ScalarArrayOpExpr);
-	saopexpr->opno = matchOpno;
-	saopexpr->opfuncid = get_opcode(matchOpno);
-	saopexpr->hashfuncid = InvalidOid;
-	saopexpr->negfuncid = InvalidOid;
-	saopexpr->useOr = true;
-	saopexpr->inputcollid = inputcollid;
-	saopexpr->args = list_make2(indexExpr, arrayNode);
-	saopexpr->location = -1;
+	saopexpr = makeSAOPArrayExpr(matchOpno, indexExpr,
+								 index->opcintype[indexcol],
+								 inputcollid, consts, haveNonConst);
 
 	/*
 	 * Finally, build an IndexClause based on the SAOP node.  Use
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 43dfecfb47f..1fb60fa1d18 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -40,7 +40,9 @@
 #include "optimizer/planmain.h"
 #include "parser/analyze.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
 #include "parser/parse_func.h"
+#include "parser/parse_oper.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
 #include "tcop/tcopprot.h"
@@ -5439,3 +5441,177 @@ pull_paramids_walker(Node *node, Bitmapset **context)
 	}
 	return expression_tree_walker(node, pull_paramids_walker, context);
 }
+
+/* The function returns compatible binary operator foe two input types
+ * otherwice returns InvalidOid.
+ * In addition, the function finds two target types for
+ * both sides and it stores them in target_arg1 and target_arg2.
+ * Besides it returns opfuncid.
+ */
+static int
+get_compatible_oper_ids(List *op,
+						Oid arg1, Oid arg2,
+						Oid *target_arg1, Oid *target_arg2,
+						Oid *opfuncid)
+{
+	Operator tup;
+	Form_pg_operator opform;
+	Oid tup_id;
+
+	/* We need to form compatible union operator for both sides
+	 * and only when we work with Const type.
+	 */
+	tup = compatible_oper(NULL, op, arg1, arg2, true, -1);
+
+	if (!HeapTupleIsValid(tup))
+		return InvalidOid;
+	opform = (Form_pg_operator)GETSTRUCT(tup);
+
+	/* If it's not a shell, we will give up */
+	if (!RegProcedureIsValid(opform->oprcode) ||
+		!IsBinaryCoercible(arg1, opform->oprleft) ||
+		!IsBinaryCoercible(arg2, opform->oprright))
+		tup_id = InvalidOid;
+	else
+		tup_id = oprid(tup);
+
+	*target_arg1 = opform->oprleft;
+	*target_arg2 = opform->oprright;
+	*opfuncid = opform->oprcode;
+
+	ReleaseSysCache(tup);
+	return tup_id;
+}
+
+ScalarArrayOpExpr *
+makeSAOPArrayExpr(Oid oper, Node *leftexpr, Oid coltype,
+				  Oid inputcollid, List *consts, bool haveNonConst)
+{
+	Oid			arraytype1;
+	HeapTuple	opertup;
+	char	   *oprname;
+	ScalarArrayOpExpr *saopexpr;
+	Oid input_typeids[2];
+	Oid target_typeids[2];
+	Oid arraytype;
+	Form_pg_operator operform;
+	Node *arrayNode;
+
+	input_typeids[0] = exprType(leftexpr);
+	input_typeids[1] = select_common_type(NULL, consts, "VALUES", NULL);
+
+	/* Lookup for operator to fetch necessary information for the SAOP node */
+	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(oper));
+
+	if (!HeapTupleIsValid(opertup))
+		elog(ERROR, "cache lookup failed for operator %u", oper);
+
+	operform = (Form_pg_operator) GETSTRUCT(opertup);
+	oprname = NameStr(operform->oprname);
+
+	/* Build the SAOP expression node */
+	saopexpr = makeNode(ScalarArrayOpExpr);
+
+	/*
+	 * Get compatible operator id for ScalarArrayOp type and
+	 * types for both sides
+	 */
+	saopexpr->opno = get_compatible_oper_ids(list_make1(makeString(oprname)),
+											 input_typeids[0], input_typeids[1],
+											 &(target_typeids[0]), &(target_typeids[1]),
+											 &(saopexpr->opfuncid));
+
+	if (saopexpr->opno == InvalidOid)
+		goto end;
+
+	arraytype = get_array_type(input_typeids[1]);
+	if (!OidIsValid(arraytype))
+		goto end;
+
+	/*
+	 * Assemble an array from the list of constants.  It seems more profitable
+	 * to build a const array.  But in the presence of other nodes, we don't
+	 * have a specific value here and must employ an ArrayExpr instead.
+	 */
+	if (haveNonConst)
+	{
+		ArrayExpr  *arrayExpr = makeNode(ArrayExpr);
+
+		/* array_collid will be set by parse_collate.c */
+		arrayExpr->element_typeid = input_typeids[1];
+		arrayExpr->array_typeid = arraytype;
+		arrayExpr->multidims = false;
+		arrayExpr->elements = consts;
+		arrayExpr->location = -1;
+
+		arrayNode = (Node *) arrayExpr;
+	}
+	else
+	{
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+		Datum	   *elems;
+		int			i = 0;
+		ArrayType  *arrayConst;
+
+		get_typlenbyvalalign(input_typeids[1], &typlen, &typbyval, &typalign);
+
+		elems = (Datum *) palloc(sizeof(Datum) * list_length(consts));
+		foreach_node(Const, value, consts)
+		{
+			Assert(!value->constisnull);
+
+			elems[i++] = value->constvalue;
+		}
+
+		arrayConst = construct_array(elems, i, input_typeids[1],
+									 typlen, typbyval, typalign);
+		arrayNode = (Node *) makeConst(arraytype, -1, inputcollid,
+									   -1, PointerGetDatum(arrayConst),
+									   false, false);
+
+		pfree(elems);
+		list_free(consts);
+	}
+
+	/* Unlike parameters constants can be coerced to target type by the user.
+	 * After finding a compatible operator to implement operation accounting
+	 * coercion, we must prepare the expression's left side.
+	 */
+	leftexpr = coerce_to_target_type(NULL,
+									 leftexpr,
+									 input_typeids[0],
+									 target_typeids[0], -1,
+									 COERCION_IMPLICIT,
+									 COERCE_IMPLICIT_CAST,
+									 -1);
+
+	arraytype1 = get_array_type(target_typeids[1]);
+	arrayNode = coerce_to_target_type(NULL,
+									arrayNode,
+									arraytype,
+									arraytype1,
+									-1,
+									COERCION_IMPLICIT,
+									COERCE_IMPLICIT_CAST,
+									-1);
+
+	inputcollid = select_common_collation(NULL,
+										  list_make2(leftexpr, arrayNode), false);
+
+	saopexpr->negfuncid = InvalidOid;
+	saopexpr->hashfuncid = InvalidOid;
+	saopexpr->useOr = true;
+	saopexpr->inputcollid = inputcollid;
+	saopexpr->args = list_make2(leftexpr, arrayNode);
+	saopexpr->location = -1;
+
+end:
+	ReleaseSysCache(opertup);
+
+	if (saopexpr->args != NIL)
+		return saopexpr;
+	else
+		return NULL;
+}
\ No newline at end of file
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 78e05d88c8e..8acb8846c72 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -206,5 +206,7 @@ extern int	locate_var_of_level(Node *node, int levelsup);
 extern List *pull_var_clause(Node *node, int flags);
 extern Node *flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node);
 extern Node *flatten_group_exprs(PlannerInfo *root, Query *query, Node *node);
+extern ScalarArrayOpExpr * makeSAOPArrayExpr(Oid oper, Node *leftexpr, Oid coltype,
+											 Oid inputcollid, List *consts, bool haveNonConst);
 
 #endif							/* OPTIMIZER_H */
-- 
2.34.1

