Hello Tom, Ashutosh,

On 07.05.2018 18:16, Tom Lane wrote:
Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> writes:
Is there a way we can improve unnest() and array_agg() to match
the performance you have specified by let's say optimizing the
cases specially when those two are used together. Identifying that
may be some work, but will not require introducing new syntax.

+1.  The first thing I thought on seeing this proposal was "I wonder
how long it will be before the SQL committee introduces some syntax
that uses the MAP keyword and breaks this".

ISTM the planner could be taught to notice the combination of unnest
and array_agg and produce a special output plan from that.

It is, however, fair to wonder whether this is worth our time to
optimize.  I've not noticed a lot of people complaining about
performance of this sort of thing, at least not since we fixed
array_agg to not be O(N^2).

The main point of this patch was about convenience; the performance
thing came out later just as a side effect :) Many users are familiar
with "map/reduce/filter" concept that many languages (not only
functional ones) utilized. And my though was that it would be great to
have those in postgres too.

Apparently there is also a case when unnest/array_agg may not produce
the result we're looking for. I mean multidimensional arrays. E.g.

select array_agg(x * 2)
from unnest(array[[1,2],[3,4],[5,6]]) as x;
    array_agg
-----------------
 {2,4,6,8,10,12}
(1 row)

select map(x * 2 for x in array[[1,2],[3,4],[5,6]]);
       ?column?
-----------------------
 {{2,4},{6,8},{10,12}}
(1 row)

array_agg produces plain arrays no matter what the input was.

There is a new version of the patch in the attachment which introduces
arbitrary per-element expressions (instead of single function call). So
now user can specify a placeholder representing one element of the array
and use it in the expressions. Like following:

select map (pow(x, 2) - 1 for x in array[1,2,3]);
   ?column?
---------------
 {1,3,7,15,31}
(1 row)


--
Ildar Musin
i.mu...@postgrespro.ru
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e284fd7..85d76b2 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -886,6 +886,56 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				break;
 			}
 
+		case T_MapExpr:
+			{
+				MapExpr	   *map = (MapExpr *) node;
+				ExprState  *elemstate;
+				Oid			resultelemtype;
+
+				ExecInitExprRec(map->arrexpr, state, resv, resnull);
+
+				resultelemtype = get_element_type(map->resulttype);
+				if (!OidIsValid(resultelemtype))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							 errmsg("target type is not an array")));
+
+				/* Construct a sub-expression for the per-element expression */
+				elemstate = makeNode(ExprState);
+				elemstate->expr = map->elemexpr;
+				elemstate->parent = state->parent;
+				elemstate->ext_params = state->ext_params;
+				elemstate->innermost_caseval = (Datum *) palloc(sizeof(Datum));
+				elemstate->innermost_casenull = (bool *) palloc(sizeof(bool));
+
+				ExecInitExprRec(map->elemexpr, elemstate,
+								&elemstate->resvalue, &elemstate->resnull);
+
+				/* Append a DONE step and ready the subexpression */
+				scratch.opcode = EEOP_DONE;
+				ExprEvalPushStep(elemstate, &scratch);
+				ExecReadyExpr(elemstate);
+
+				scratch.opcode = EEOP_MAP;
+				scratch.d.map.elemexprstate = elemstate;
+				scratch.d.map.resultelemtype = resultelemtype;
+
+				if (elemstate)
+				{
+					/* Set up workspace for array_map */
+					scratch.d.map.amstate =
+						(ArrayMapState *) palloc0(sizeof(ArrayMapState));
+				}
+				else
+				{
+					/* Don't need workspace if there's no subexpression */
+					scratch.d.map.amstate = NULL;
+				}
+
+				ExprEvalPushStep(state, &scratch);
+				break;
+			}
+
 		case T_OpExpr:
 			{
 				OpExpr	   *op = (OpExpr *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9d6e25a..b2cbc45 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -328,6 +328,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		&&CASE_EEOP_FUNCEXPR_STRICT,
 		&&CASE_EEOP_FUNCEXPR_FUSAGE,
 		&&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+		&&CASE_EEOP_MAP,
 		&&CASE_EEOP_BOOL_AND_STEP_FIRST,
 		&&CASE_EEOP_BOOL_AND_STEP,
 		&&CASE_EEOP_BOOL_AND_STEP_LAST,
@@ -699,6 +700,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			EEO_NEXT();
 		}
 
+		EEO_CASE(EEOP_MAP)
+		{
+			ExecEvalMapExpr(state, op, econtext);
+
+			EEO_NEXT();
+		}
+
 		/*
 		 * If any of its clauses is FALSE, an AND's result is FALSE regardless
 		 * of the states of the rest of the clauses, so we can stop evaluating
@@ -2230,6 +2238,33 @@ ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
 }
 
 /*
+ * Evaluate a MapExpr expression.
+ *
+ * Source array is in step's result variable.
+ */
+void
+ExecEvalMapExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+	Datum		arraydatum;
+
+	/* NULL array -> NULL result */
+	if (*op->resnull)
+		return;
+
+	arraydatum = *op->resvalue;
+	Assert(op->d.map.elemexprstate != NULL);
+
+	/*
+	 * Use array_map to apply the sub-expression to each array element.
+	 */
+	*op->resvalue = array_map(arraydatum,
+							  op->d.map.elemexprstate,
+							  econtext,
+							  op->d.map.resultelemtype,
+							  op->d.map.amstate);
+}
+
+/*
  * Evaluate a PARAM_EXEC parameter.
  *
  * PARAM_EXEC params (internal executor parameters) are stored in the
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c045a7..c877c29 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2621,6 +2621,32 @@ _copyFuncCall(const FuncCall *from)
 	return newnode;
 }
 
+static A_MapExpr *
+_copyAMapExpr(const A_MapExpr *from)
+{
+	A_MapExpr   *newnode = makeNode(A_MapExpr);
+
+	COPY_NODE_FIELD(elemexpr);
+	COPY_STRING_FIELD(placeholder);
+	COPY_NODE_FIELD(arrexpr);
+	COPY_LOCATION_FIELD(location);
+
+	return newnode;
+}
+
+static MapExpr *
+_copyMapExpr(const MapExpr *from)
+{
+	MapExpr   *newnode = makeNode(MapExpr);
+
+	COPY_NODE_FIELD(elemexpr);
+	COPY_NODE_FIELD(arrexpr);
+	COPY_SCALAR_FIELD(resulttype);
+	COPY_SCALAR_FIELD(resultcollid);
+
+	return newnode;
+}
+
 static A_Star *
 _copyAStar(const A_Star *from)
 {
@@ -5496,6 +5522,12 @@ copyObjectImpl(const void *from)
 		case T_FuncCall:
 			retval = _copyFuncCall(from);
 			break;
+		case T_A_MapExpr:
+			retval = _copyAMapExpr(from);
+			break;
+		case T_MapExpr:
+			retval = _copyMapExpr(from);
+			break;
 		case T_A_Star:
 			retval = _copyAStar(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6a971d0..18e2555 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2350,6 +2350,28 @@ _equalFuncCall(const FuncCall *a, const FuncCall *b)
 }
 
 static bool
+_equalAMapExpr(const A_MapExpr *a, const A_MapExpr *b)
+{
+	COMPARE_NODE_FIELD(elemexpr);
+	COMPARE_STRING_FIELD(placeholder);
+	COMPARE_NODE_FIELD(arrexpr);
+	COMPARE_LOCATION_FIELD(location);
+
+	return true;
+}
+
+static bool
+_equalMapExpr(const MapExpr *a, const MapExpr *b)
+{
+	COMPARE_NODE_FIELD(elemexpr);
+	COMPARE_NODE_FIELD(arrexpr);
+	COMPARE_SCALAR_FIELD(resulttype);
+	COMPARE_SCALAR_FIELD(resultcollid);
+
+	return true;
+}
+
+static bool
 _equalAStar(const A_Star *a, const A_Star *b)
 {
 	return true;
@@ -3573,6 +3595,12 @@ equal(const void *a, const void *b)
 		case T_FuncCall:
 			retval = _equalFuncCall(a, b);
 			break;
+		case T_A_MapExpr:
+			retval = _equalAMapExpr(a, b);
+			break;
+		case T_MapExpr:
+			retval = _equalMapExpr(a, b);
+			break;
 		case T_A_Star:
 			retval = _equalAStar(a, b);
 			break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index a10014f..95a3844 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -80,6 +80,8 @@ exprType(const Node *expr)
 		case T_FuncExpr:
 			type = ((const FuncExpr *) expr)->funcresulttype;
 			break;
+		case T_MapExpr:
+			return ((MapExpr *) expr)->resulttype;
 		case T_NamedArgExpr:
 			type = exprType((Node *) ((const NamedArgExpr *) expr)->arg);
 			break;
@@ -750,6 +752,9 @@ exprCollation(const Node *expr)
 		case T_FuncExpr:
 			coll = ((const FuncExpr *) expr)->funccollid;
 			break;
+		case T_MapExpr:
+			coll = ((const MapExpr *) expr)->resultcollid;
+			break;
 		case T_NamedArgExpr:
 			coll = exprCollation((Node *) ((const NamedArgExpr *) expr)->arg);
 			break;
@@ -994,6 +999,9 @@ exprSetCollation(Node *expr, Oid collation)
 		case T_FuncExpr:
 			((FuncExpr *) expr)->funccollid = collation;
 			break;
+		case T_MapExpr:
+			((MapExpr *) expr)->resultcollid = collation;
+			break;
 		case T_NamedArgExpr:
 			Assert(collation == exprCollation((Node *) ((NamedArgExpr *) expr)->arg));
 			break;
@@ -1132,6 +1140,10 @@ exprSetInputCollation(Node *expr, Oid inputcollation)
 		case T_FuncExpr:
 			((FuncExpr *) expr)->inputcollid = inputcollation;
 			break;
+		case T_MapExpr:
+			exprSetInputCollation((Node *) ((MapExpr *) expr)->elemexpr,
+								  inputcollation);
+			break;
 		case T_OpExpr:
 			((OpExpr *) expr)->inputcollid = inputcollation;
 			break;
@@ -1937,6 +1949,16 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
+		case T_MapExpr:
+			{
+				MapExpr	   *map = (MapExpr *) node;
+
+				if (walker((Node *) map->arrexpr, context))
+					return true;
+				if (walker((Node *) map->elemexpr, context))
+					return true;
+			}
+			break;
 		case T_NamedArgExpr:
 			return walker(((NamedArgExpr *) node)->arg, context);
 		case T_OpExpr:
@@ -2479,6 +2501,9 @@ expression_tree_mutator(Node *node,
 		case T_NextValueExpr:
 		case T_RangeTblRef:
 		case T_SortGroupClause:
+		case T_ColumnRef:
+		case T_ParamRef:
+		case T_A_Const:
 			return (Node *) copyObject(node);
 		case T_WithCheckOption:
 			{
@@ -2566,6 +2591,15 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_MapExpr:
+			{
+				MapExpr	   *expr = (MapExpr *) node;
+				MapExpr	   *newnode;
+
+				FLATCOPY(newnode, expr, MapExpr);
+				MUTATE(newnode->arrexpr, expr->arrexpr, Expr *);
+				return (Node *) newnode;
+			}
 		case T_NamedArgExpr:
 			{
 				NamedArgExpr *nexpr = (NamedArgExpr *) node;
@@ -3060,6 +3094,36 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_A_Expr:
+			{
+				A_Expr	   *expr = (A_Expr *) node;
+				A_Expr	   *newnode;
+
+				FLATCOPY(newnode, expr, A_Expr);
+				MUTATE(newnode->lexpr, expr->lexpr, Node *);
+				MUTATE(newnode->rexpr, expr->rexpr, Node *);
+				return (Node *) newnode;
+			}
+			break;
+		case T_FuncCall:
+			{
+				FuncCall   *fc = (FuncCall *) node;
+				FuncCall   *newnode;
+
+				FLATCOPY(newnode, fc, FuncCall);
+				MUTATE(newnode->args, fc->args, List *);
+				return (Node *) newnode;
+			}
+		case T_TypeCast:
+			{
+				TypeCast   *tc = (TypeCast *) node;
+				TypeCast   *newnode;
+
+				FLATCOPY(newnode, tc, TypeCast);
+				MUTATE(newnode->arg, tc->arg, Node *);
+				return (Node *) newnode;
+			}
+
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1da9d7e..98b6740 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2793,6 +2793,28 @@ _outFuncCall(StringInfo str, const FuncCall *node)
 }
 
 static void
+_outAMapExpr(StringInfo str, const A_MapExpr *node)
+{
+	WRITE_NODE_TYPE("A_MAPEXPR");
+
+	WRITE_NODE_FIELD(elemexpr);
+	WRITE_STRING_FIELD(placeholder);
+	WRITE_NODE_FIELD(arrexpr);
+	WRITE_LOCATION_FIELD(location);
+}
+
+static void
+_outMapExpr(StringInfo str, const MapExpr *node)
+{
+	WRITE_NODE_TYPE("MAPEXPR");
+
+	WRITE_NODE_FIELD(elemexpr);
+	WRITE_NODE_FIELD(arrexpr);
+	WRITE_OID_FIELD(resulttype);
+	WRITE_OID_FIELD(resultcollid);
+}
+
+static void
 _outDefElem(StringInfo str, const DefElem *node)
 {
 	WRITE_NODE_TYPE("DEFELEM");
@@ -4278,6 +4300,12 @@ outNode(StringInfo str, const void *obj)
 			case T_FuncCall:
 				_outFuncCall(str, obj);
 				break;
+			case T_A_MapExpr:
+				_outAMapExpr(str, obj);
+				break;
+			case T_MapExpr:
+				_outMapExpr(str, obj);
+				break;
 			case T_DefElem:
 				_outDefElem(str, obj);
 				break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index babb62d..37a6e1c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -563,6 +563,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	func_application func_expr_common_subexpr
 %type <node>	func_expr func_expr_windowless
+%type <node>	map_expr
 %type <node>	common_table_expr
 %type <with>	with_clause opt_with_clause
 %type <list>	cte_list
@@ -652,7 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
+	MAP MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@@ -13461,6 +13462,8 @@ c_expr:		columnref								{ $$ = $1; }
 				}
 			| case_expr
 				{ $$ = $1; }
+			| map_expr
+				{ $$ = $1; }
 			| func_expr
 				{ $$ = $1; }
 			| select_with_parens			%prec UMINUS
@@ -13556,6 +13559,17 @@ c_expr:		columnref								{ $$ = $1; }
 			  }
 		;
 
+map_expr:	MAP '(' a_expr FOR IDENT IN_P a_expr ')'
+				{
+					A_MapExpr *m = makeNode(A_MapExpr);
+					m->elemexpr = $3;
+					m->placeholder = $5;
+					m->arrexpr = $7;
+					m->location = @1;
+					$$ = (Node *)m;
+				}
+		;
+
 func_application: func_name '(' ')'
 				{
 					$$ = (Node *) makeFuncCall($1, NIL, @1);
@@ -15423,6 +15437,7 @@ reserved_keyword:
 			| LIMIT
 			| LOCALTIME
 			| LOCALTIMESTAMP
+			| MAP
 			| NOT
 			| NULL_P
 			| OFFSET
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 6d34245..0021b24 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -667,6 +667,15 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 															&loccontext);
 						}
 						break;
+					case T_MapExpr:
+						{
+							MapExpr	   *map = (MapExpr *) node;
+
+							(void) expression_tree_walker((Node *) map->elemexpr,
+														  assign_collations_walker,
+														  (void *) &loccontext);
+						}
+						break;
 					default:
 
 						/*
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 385e54a..18e7310 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -101,6 +101,9 @@ static Node *transformAExprIn(ParseState *pstate, A_Expr *a);
 static Node *transformAExprBetween(ParseState *pstate, A_Expr *a);
 static Node *transformBoolExpr(ParseState *pstate, BoolExpr *a);
 static Node *transformFuncCall(ParseState *pstate, FuncCall *fn);
+static Node *convert_placeholder_mutator(Node *node, 
+							convert_placeholder_context *context);
+static Node *transformMapExpr(ParseState *pstate, A_MapExpr *a_mapexpr);
 static Node *transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref);
 static Node *transformCaseExpr(ParseState *pstate, CaseExpr *c);
 static Node *transformSubLink(ParseState *pstate, SubLink *sublink);
@@ -135,6 +138,14 @@ static void emit_precedence_warnings(ParseState *pstate,
 						 Node *lchild, Node *rchild,
 						 int location);
 
+/* Context for convert_placeholder_mutator */
+typedef struct
+{
+	char   *placeholder;
+	Oid		typid;
+	Oid		typmod;
+	Oid		collid;
+} convert_placeholder_context;
 
 /*
  * transformExpr -
@@ -258,6 +269,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_A_MapExpr:
+			result = transformMapExpr(pstate, (A_MapExpr *) expr);
+			break;
+
 		case T_BoolExpr:
 			result = transformBoolExpr(pstate, (BoolExpr *) expr);
 			break;
@@ -1486,6 +1501,99 @@ transformFuncCall(ParseState *pstate, FuncCall *fn)
 }
 
 static Node *
+convert_placeholder(Node *node, char *placeholder,
+					Oid typid, Oid typmod, Oid collid)
+{
+	convert_placeholder_context context;
+
+	context.placeholder = placeholder;
+	context.typid = typid;
+	context.typmod = typmod;
+	context.collid = collid;
+
+	return convert_placeholder_mutator(node, &context);
+}
+
+static Node *
+convert_placeholder_mutator(Node *node,
+							convert_placeholder_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, ColumnRef))
+	{
+		ColumnRef  *cref = (ColumnRef *) node;
+		char	   *refname;
+
+		if (list_length(cref->fields) != 1)
+			return node;
+
+		refname = strVal(linitial(cref->fields));
+
+		if (strcmp(refname, context->placeholder) == 0)
+		{
+			CaseTestExpr *placeholder;
+
+			/* Create placeholder for per-element expression */
+			placeholder = makeNode(CaseTestExpr);
+			placeholder->typeId = context->typid;
+			placeholder->typeMod = context->typmod;
+			placeholder->collation = context->collid;
+
+			return (Node *) placeholder;
+		}
+	}
+
+	return expression_tree_mutator(node,
+								   convert_placeholder_mutator,
+								   (void *) context);
+}
+
+static Node *
+transformMapExpr(ParseState *pstate, A_MapExpr *a_mapexpr)
+{
+	MapExpr	   *map;
+	CaseTestExpr *placeholder;
+	Node	   *arrexpr;
+	Oid			funcId;
+	Oid			sourceElemType;
+	Oid			targetElemType;
+	Node	   *elemexpr;
+	Oid			collation;
+
+	/* Transfotm array expression */
+	arrexpr = transformExprRecurse(pstate, a_mapexpr->arrexpr);
+
+	/* Determine element type and collation */
+	sourceElemType = get_element_type(exprType(arrexpr));
+	if (sourceElemType == InvalidOid)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("array expression is expected"),
+				 parser_errposition(pstate, exprLocation(a_mapexpr->arrexpr))));
+	collation = IsA(arrexpr, ArrayExpr) ?
+		get_typcollation(sourceElemType) :
+		exprCollation(arrexpr);
+
+	/* Replace placeholder ColumnRef by CaseTestExpr */
+	elemexpr = convert_placeholder(a_mapexpr->elemexpr,
+									a_mapexpr->placeholder,
+									sourceElemType,
+									exprTypmod(arrexpr),
+									collation);
+	/* Transform per-element expression */
+	elemexpr = transformExprRecurse(pstate, elemexpr);
+
+	/* Build resulting MapExpr */
+	map = makeNode(MapExpr);
+	map->arrexpr = (Expr *) arrexpr;
+	map->elemexpr = (Expr *) elemexpr;
+	map->resulttype = get_array_type(exprType(elemexpr));
+
+	return (Node *) map;
+}
+
+static Node *
 transformMultiAssignRef(ParseState *pstate, MultiAssignRef *maref)
 {
 	SubLink    *sublink;
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index f7b1f77..ddad247 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -92,6 +92,11 @@ typedef enum ExprEvalOp
 	EEOP_FUNCEXPR_STRICT_FUSAGE,
 
 	/*
+	 * Evaluate map expression
+	 */
+	EEOP_MAP,
+
+	/*
 	 * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
 	 * subexpressions are special-cased for performance.  Since AND always has
 	 * at least two subexpressions, FIRST and LAST never apply to the same
@@ -318,6 +323,13 @@ typedef struct ExprEvalStep
 			int			nargs;	/* number of arguments */
 		}			func;
 
+		struct
+		{
+			ExprState  *elemexprstate;	/* null if no per-element work */
+			Oid			resultelemtype; /* element type of result array */
+			struct ArrayMapState *amstate;	/* workspace for array_map */
+		}			map;
+
 		/* for EEOP_BOOL_*_STEP */
 		struct
 		{
@@ -695,6 +707,8 @@ extern void ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op,
 					   ExprContext *econtext);
 extern void ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op,
 							 ExprContext *econtext);
+extern void ExecEvalMapExpr(ExprState *state, ExprEvalStep *op,
+				  ExprContext *econtext);
 extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
 				  ExprContext *econtext);
 extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index adb159a..d34abc2 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -428,6 +428,8 @@ typedef enum NodeTag
 	T_ParamRef,
 	T_A_Const,
 	T_FuncCall,
+	T_A_MapExpr,
+	T_MapExpr,
 	T_A_Star,
 	T_A_Indices,
 	T_A_Indirection,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6390f7e..be4b2d1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -418,6 +418,18 @@ typedef struct A_ArrayExpr
 } A_ArrayExpr;
 
 /*
+ * A_MapExpr - array map expression
+ */
+typedef struct A_MapExpr
+{
+	NodeTag		type;
+	Node	   *elemexpr;
+	char	   *placeholder;
+	Node	   *arrexpr;
+	int			location;		/* token location, or -1 if unknown */
+} A_MapExpr;
+
+/*
  * ResTarget -
  *	  result target (used in target list of pre-transformed parse trees)
  *
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f90aa7b..f108898 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -460,6 +460,18 @@ typedef struct FuncExpr
 } FuncExpr;
 
 /*
+ * MapExpr - array map expression
+ */
+typedef struct MapExpr
+{
+	NodeTag		type;
+	Expr	   *elemexpr;		/* expression representing per-element work */
+	Expr	   *arrexpr;		/* source expression of array type */
+	Oid			resulttype;		/* OID of target array type */
+	Oid			resultcollid;	/* OID of collation, or InvalidOid if none */
+} MapExpr;
+
+/*
  * NamedArgExpr - a named argument of a function
  *
  * This node type can only appear in the args list of a FuncCall or FuncExpr
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 23db401..d6c5254 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -243,6 +243,7 @@ PG_KEYWORD("location", LOCATION, UNRESERVED_KEYWORD)
 PG_KEYWORD("lock", LOCK_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("locked", LOCKED, UNRESERVED_KEYWORD)
 PG_KEYWORD("logged", LOGGED, UNRESERVED_KEYWORD)
+PG_KEYWORD("map", MAP, RESERVED_KEYWORD)
 PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
 PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)

Reply via email to