public inbox for [email protected]  
help / color / mirror / Atom feed
Tightly packing expressions
80+ messages / 7 participants
[nested] [flat]

* Tightly packing expressions
@ 2017-08-03 18:01  Douglas Doole <[email protected]>
  0 siblings, 1 reply; 80+ messages in thread

From: Douglas Doole @ 2017-08-03 18:01 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; pgsql-hackers

Andres,
Back when you were getting ready to commit your faster expressions, I
volunteered to look at switching from an array of fixed sized steps to
tightly packed structures. (
https://www.postgresql.org/message-id/[email protected]).
I've finally found time to make it happen.

Using tightly packed structures makes the expressions a lot smaller. I
instrumented some before and after builds and ran "make check" just to see
how much memory expressions were using. What I found was:

There were ~104K expressions.

Memory - bytes needed to hold the steps of the expressions
Allocated Memory - memory is allocated in blocks, this is the bytes
allocated to hold the expressions
Wasted Memory - the difference between the allocated and used memory

Original code:
Memory: min=64 max=9984 total=28232512 average=265
Allocated Memory: min=1024 max=16384 total=111171584 average=1045
Wasted Memory: min=0 max=6400 total=82939072 average=780

New code:
Memory: min=32 (50%) max=8128 (82%) total=18266712 (65%) average=175 (66%)
Allocated Memory: min=192 (19%) max=9408 (57%) total=29386176 (26%)
average=282 (27%)
Wasted Memory: min=0 (0%) max=1280 (20%) total=11119464 (13%) average=106
(14%)

So on average expressions are about a third smaller than with the fixed
size array. The new approach doesn't overallocate as badly as the array
approach so the packed approach cuts memory consumption by over 70%. (Of
course, the array code could be tuned to reduce the overhead as well.)

Another benefit of the way I did this is that the expression memory is
never reallocated. This means that it is safe for one step to point
directly to a field in another step without needing to separately palloc
storage. That should allow us to simplify the code and reduce pointer
traversals. (I haven't exploited this in the patch. I figured it would be a
task for round 2 assuming you like what I've done here.)

The work was mostly mechanical. The only tricky bit was dealing with the
places where you jump to step n+1 while building step n. Since we can't
tell the address of step n+1 until it is actually built, I had to defer
resolution of the jumps. So all the interesting bits of this patch are in
ExprEvalPushStep().

Let me know what you think.

- Doug
Salesforce


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers


Attachments:

  [text/x-patch] exprBuild.patch (257.4K, ../../CADE5jY+p2c-vPPK4zG1EyJz=Ttsak3WB+mCfYOAYMxb=ZZSxbQ@mail.gmail.com/3-exprBuild.patch)
  download | inline diff:
*** a/src/backend/executor/execExpr.c
--- b/src/backend/executor/execExpr.c
***************
*** 45,50 ****
--- 45,51 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/typcache.h"
+ #include "sys/param.h"
  
  
  typedef struct LastAttnumInfo
***************
*** 57,78 **** typedef struct LastAttnumInfo
  static void ExecReadyExpr(ExprState *state);
  static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				Datum *resv, bool *resnull);
! static void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
! static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
  			 Oid funcid, Oid inputcollid, PlanState *parent,
! 			 ExprState *state);
  static void ExecInitExprSlots(ExprState *state, Node *node);
  static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
! static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
! 					PlanState *parent);
! static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
  				 PlanState *parent, ExprState *state,
  				 Datum *resv, bool *resnull);
  static bool isAssignmentIndirectionExpr(Expr *expr);
! static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
  					   PlanState *parent, ExprState *state,
  					   Datum *resv, bool *resnull);
  
  
  /*
   * ExecInitExpr: prepare an expression tree for execution
--- 58,166 ----
  static void ExecReadyExpr(ExprState *state);
  static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				Datum *resv, bool *resnull);
! static ExprEvalStep *ExprEvalPushStep(ExprState *es, const ExprEvalOp opcode,
!                                       Datum *resv, bool *resnull);
! static void ExecInitFunc(Expr *node, List *args,
  			 Oid funcid, Oid inputcollid, PlanState *parent,
! 			 ExprState *state, Datum *resv, bool *resnull);
  static void ExecInitExprSlots(ExprState *state, Node *node);
  static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
! static void ExecInitWholeRowVar(ExprState *state, Var *variable,
!                                 PlanState *parent, Datum *resv, bool *resnull);
! static void ExecInitArrayRef(ArrayRef *aref,
  				 PlanState *parent, ExprState *state,
  				 Datum *resv, bool *resnull);
  static bool isAssignmentIndirectionExpr(Expr *expr);
! static void ExecInitCoerceToDomain(CoerceToDomain *ctest,
  					   PlanState *parent, ExprState *state,
  					   Datum *resv, bool *resnull);
  
+ /*
+  * Lookup array for the size of each op
+  */
+ #define EEOPSIZE(t) MAXALIGN(sizeof(t))
+ static size_t eeop_size[] =
+ {
+ 	/* EEOP_DONE                   */ EEOPSIZE(ExprEvalStep),
+ 	/* EEOP_INNER_FETCHSOME        */ EEOPSIZE(ExprEvalStep_fetch),
+ 	/* EEOP_OUTER_FETCHSOME        */ EEOPSIZE(ExprEvalStep_fetch),
+ 	/* EEOP_SCAN_FETCHSOME         */ EEOPSIZE(ExprEvalStep_fetch),
+ 	/* EEOP_INNER_VAR_FIRST        */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_INNER_VAR              */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_OUTER_VAR_FIRST        */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_OUTER_VAR              */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_SCAN_VAR_FIRST         */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_SCAN_VAR               */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_INNER_SYSVAR           */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_OUTER_SYSVAR           */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_SCAN_SYSVAR            */ EEOPSIZE(ExprEvalStep_var),
+ 	/* EEOP_WHOLEROW               */ EEOPSIZE(ExprEvalStep_wholerow),
+ 	/* EEOP_ASSIGN_INNER_VAR       */ EEOPSIZE(ExprEvalStep_assign_var),
+ 	/* EEOP_ASSIGN_OUTER_VAR       */ EEOPSIZE(ExprEvalStep_assign_var),
+ 	/* EEOP_ASSIGN_SCAN_VAR        */ EEOPSIZE(ExprEvalStep_assign_var),
+ 	/* EEOP_ASSIGN_TMP             */ EEOPSIZE(ExprEvalStep_assign_tmp),
+ 	/* EEOP_ASSIGN_TMP_MAKE_RO     */ EEOPSIZE(ExprEvalStep_assign_tmp),
+ 	/* EEOP_CONST                  */ EEOPSIZE(ExprEvalStep_constval),
+ 	/* EEOP_FUNCEXPR               */ EEOPSIZE(ExprEvalStep_func),
+ 	/* EEOP_FUNCEXPR_STRICT        */ EEOPSIZE(ExprEvalStep_func),
+ 	/* EEOP_FUNCEXPR_FUSAGE        */ EEOPSIZE(ExprEvalStep_func),
+ 	/* EEOP_FUNCEXPR_STRICT_FUSAGE */ EEOPSIZE(ExprEvalStep_func),
+ 	/* EEOP_BOOL_AND_STEP_FIRST    */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_BOOL_AND_STEP          */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_BOOL_AND_STEP_LAST     */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_BOOL_OR_STEP_FIRST     */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_BOOL_OR_STEP           */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_BOOL_OR_STEP_LAST      */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_BOOL_NOT_STEP          */ EEOPSIZE(ExprEvalStep_boolexpr),
+ 	/* EEOP_QUAL                   */ EEOPSIZE(ExprEvalStep_qualexpr),
+ 	/* EEOP_JUMP                   */ EEOPSIZE(ExprEvalStep_jump),
+ 	/* EEOP_JUMP_IF_NULL           */ EEOPSIZE(ExprEvalStep_jump),
+ 	/* EEOP_JUMP_IF_NOT_NULL       */ EEOPSIZE(ExprEvalStep_jump),
+ 	/* EEOP_JUMP_IF_NOT_TRUE       */ EEOPSIZE(ExprEvalStep_jump),
+ 	/* EEOP_NULLTEST_ISNULL        */ EEOPSIZE(ExprEvalStep_nulltest_row),
+ 	/* EEOP_NULLTEST_ISNOTNULL     */ EEOPSIZE(ExprEvalStep_nulltest_row),
+ 	/* EEOP_NULLTEST_ROWISNULL     */ EEOPSIZE(ExprEvalStep_nulltest_row),
+ 	/* EEOP_NULLTEST_ROWISNOTNULL  */ EEOPSIZE(ExprEvalStep_nulltest_row),
+ 	/* EEOP_BOOLTEST_IS_TRUE       */ EEOPSIZE(ExprEvalStep),
+ 	/* EEOP_BOOLTEST_IS_NOT_TRUE   */ EEOPSIZE(ExprEvalStep),
+ 	/* EEOP_BOOLTEST_IS_FALSE      */ EEOPSIZE(ExprEvalStep),
+ 	/* EEOP_BOOLTEST_IS_NOT_FALSE  */ EEOPSIZE(ExprEvalStep),
+ 	/* EEOP_PARAM_EXEC             */ EEOPSIZE(ExprEvalStep_param),
+ 	/* EEOP_PARAM_EXTERN           */ EEOPSIZE(ExprEvalStep_param),
+ 	/* EEOP_CASE_TESTVAL           */ EEOPSIZE(ExprEvalStep_casetest),
+ 	/* EEOP_MAKE_READONLY          */ EEOPSIZE(ExprEvalStep_make_readonly),
+ 	/* EEOP_IOCOERCE               */ EEOPSIZE(ExprEvalStep_iocoerce),
+ 	/* EEOP_DISTINCT               */ EEOPSIZE(ExprEvalStep_func),
+ 	/* EEOP_NULLIF                 */ EEOPSIZE(ExprEvalStep_func),
+ 	/* EEOP_SQLVALUEFUNCTION       */ EEOPSIZE(ExprEvalStep_sqlvaluefunction),
+ 	/* EEOP_CURRENTOFEXPR          */ EEOPSIZE(ExprEvalStep),
+ 	/* EEOP_NEXTVALUEEXPR          */ EEOPSIZE(ExprEvalStep_nextvalueexpr),
+ 	/* EEOP_ARRAYEXPR              */ EEOPSIZE(ExprEvalStep_arrayexpr),
+ 	/* EEOP_ARRAYCOERCE            */ EEOPSIZE(ExprEvalStep_arraycoerce),
+ 	/* EEOP_ROW                    */ EEOPSIZE(ExprEvalStep_row),
+ 	/* EEOP_ROWCOMPARE_STEP        */ EEOPSIZE(ExprEvalStep_rowcompare_step),
+ 	/* EEOP_ROWCOMPARE_FINAL       */ EEOPSIZE(ExprEvalStep_rowcompare_final),
+ 	/* EEOP_MINMAX                 */ EEOPSIZE(ExprEvalStep_minmax),
+ 	/* EEOP_FIELDSELECT            */ EEOPSIZE(ExprEvalStep_fieldselect),
+ 	/* EEOP_FIELDSTORE_DEFORM      */ EEOPSIZE(ExprEvalStep_fieldstore),
+ 	/* EEOP_FIELDSTORE_FORM        */ EEOPSIZE(ExprEvalStep_fieldstore),
+ 	/* EEOP_ARRAYREF_SUBSCRIPT     */ EEOPSIZE(ExprEvalStep_arrayref_subscript),
+ 	/* EEOP_ARRAYREF_OLD           */ EEOPSIZE(ExprEvalStep_arrayref),
+ 	/* EEOP_ARRAYREF_ASSIGN        */ EEOPSIZE(ExprEvalStep_arrayref),
+ 	/* EEOP_ARRAYREF_FETCH         */ EEOPSIZE(ExprEvalStep_arrayref),
+ 	/* EEOP_DOMAIN_TESTVAL         */ EEOPSIZE(ExprEvalStep_casetest),
+ 	/* EEOP_DOMAIN_NOTNULL         */ EEOPSIZE(ExprEvalStep_domaincheck),
+ 	/* EEOP_DOMAIN_CHECK           */ EEOPSIZE(ExprEvalStep_domaincheck),
+ 	/* EEOP_CONVERT_ROWTYPE        */ EEOPSIZE(ExprEvalStep_convert_rowtype),
+ 	/* EEOP_SCALARARRAYOP          */ EEOPSIZE(ExprEvalStep_scalararrayop),
+ 	/* EEOP_XMLEXPR                */ EEOPSIZE(ExprEvalStep_xmlexpr),
+ 	/* EEOP_AGGREF                 */ EEOPSIZE(ExprEvalStep_aggref),
+ 	/* EEOP_GROUPING_FUNC          */ EEOPSIZE(ExprEvalStep_grouping_func),
+ 	/* EEOP_WINDOW_FUNC            */ EEOPSIZE(ExprEvalStep_window_func),
+ 	/* EEOP_SUBPLAN                */ EEOPSIZE(ExprEvalStep_subplan),
+ 	/* EEOP_ALTERNATIVE_SUBPLAN    */ EEOPSIZE(ExprEvalStep_alternative_subplan),
+ 	/* EEOP_LAST                   */ 0
+ };
  
  /*
   * ExecInitExpr: prepare an expression tree for execution
***************
*** 113,119 **** ExprState *
  ExecInitExpr(Expr *node, PlanState *parent)
  {
  	ExprState  *state;
- 	ExprEvalStep scratch;
  
  	/* Special case: NULL expression produces a NULL ExprState pointer */
  	if (node == NULL)
--- 201,206 ----
***************
*** 130,137 **** ExecInitExpr(Expr *node, PlanState *parent)
  	ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
  
  	/* Finally, append a DONE step */
! 	scratch.opcode = EEOP_DONE;
! 	ExprEvalPushStep(state, &scratch);
  
  	ExecReadyExpr(state);
  
--- 217,223 ----
  	ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
  
  	/* Finally, append a DONE step */
! 	ExprEvalPushStep(state, EEOP_DONE, NULL, NULL);
  
  	ExecReadyExpr(state);
  
***************
*** 160,166 **** ExprState *
  ExecInitQual(List *qual, PlanState *parent)
  {
  	ExprState  *state;
! 	ExprEvalStep scratch;
  	List	   *adjust_jumps = NIL;
  	ListCell   *lc;
  
--- 246,252 ----
  ExecInitQual(List *qual, PlanState *parent)
  {
  	ExprState  *state;
! 	ExprEvalStep_qualexpr *scratch;
  	List	   *adjust_jumps = NIL;
  	ListCell   *lc;
  
***************
*** 185,197 **** ExecInitQual(List *qual, PlanState *parent)
  	 * special opcode for qual evaluation that's simpler than BOOL_AND (which
  	 * has more complex NULL handling).
  	 */
- 	scratch.opcode = EEOP_QUAL;
- 
- 	/*
- 	 * We can use ExprState's resvalue/resnull as target for each qual expr.
- 	 */
- 	scratch.resvalue = &state->resvalue;
- 	scratch.resnull = &state->resnull;
  
  	foreach(lc, qual)
  	{
--- 271,276 ----
***************
*** 200,229 **** ExecInitQual(List *qual, PlanState *parent)
  		/* first evaluate expression */
  		ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
  
! 		/* then emit EEOP_QUAL to detect if it's false (or null) */
! 		scratch.d.qualexpr.jumpdone = -1;
! 		ExprEvalPushStep(state, &scratch);
! 		adjust_jumps = lappend_int(adjust_jumps,
! 								   state->steps_len - 1);
! 	}
! 
! 	/* adjust jump targets */
! 	foreach(lc, adjust_jumps)
! 	{
! 		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
  
! 		Assert(as->opcode == EEOP_QUAL);
! 		Assert(as->d.qualexpr.jumpdone == -1);
! 		as->d.qualexpr.jumpdone = state->steps_len;
  	}
  
  	/*
  	 * At the end, we don't need to do anything more.  The last qual expr must
  	 * have yielded TRUE, and since its result is stored in the desired output
  	 * location, we're done.
  	 */
! 	scratch.opcode = EEOP_DONE;
! 	ExprEvalPushStep(state, &scratch);
  
  	ExecReadyExpr(state);
  
--- 279,307 ----
  		/* first evaluate expression */
  		ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
  
! 		/*
! 		 * Then emit EEOP_QUAL to detect if it's false (or null)
! 		 * We can use ExprState's resvalue/resnull as target for each qual expr.
! 		 */
! 		scratch = (ExprEvalStep_qualexpr *)
! 			ExprEvalPushStep(state,
! 		                     EEOP_QUAL,
! 		                     &state->resvalue,
! 		                     &state->resnull);
  
! 		scratch->jumpdone = NULL;
! 		adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  	}
  
+ 	/* Resolve the jumpdone pointers when the next operator is created */
+ 	state->adjust_jumps = adjust_jumps;
+ 
  	/*
  	 * At the end, we don't need to do anything more.  The last qual expr must
  	 * have yielded TRUE, and since its result is stored in the desired output
  	 * location, we're done.
  	 */
! 	ExprEvalPushStep(state, EEOP_DONE, NULL, NULL);
  
  	ExecReadyExpr(state);
  
***************
*** 306,312 **** ExecBuildProjectionInfo(List *targetList,
  {
  	ProjectionInfo *projInfo = makeNode(ProjectionInfo);
  	ExprState  *state;
- 	ExprEvalStep scratch;
  	ListCell   *lc;
  
  	projInfo->pi_exprContext = econtext;
--- 384,389 ----
***************
*** 363,395 **** ExecBuildProjectionInfo(List *targetList,
  
  		if (isSafeVar)
  		{
  			/* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */
  			switch (variable->varno)
  			{
  				case INNER_VAR:
  					/* get the tuple from the inner node */
! 					scratch.opcode = EEOP_ASSIGN_INNER_VAR;
  					break;
  
  				case OUTER_VAR:
  					/* get the tuple from the outer node */
! 					scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
  					break;
  
  					/* INDEX_VAR is handled by default case */
  
  				default:
  					/* get the tuple from the relation being scanned */
! 					scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
  					break;
  			}
  
! 			scratch.d.assign_var.attnum = attnum - 1;
! 			scratch.d.assign_var.resultnum = tle->resno - 1;
! 			ExprEvalPushStep(state, &scratch);
  		}
  		else
  		{
  			/*
  			 * Otherwise, compile the column expression normally.
  			 *
--- 440,482 ----
  
  		if (isSafeVar)
  		{
+ 			ExprEvalStep_assign_var *scratch;
+ 			ExprEvalOp opcode;
+ 
  			/* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */
  			switch (variable->varno)
  			{
  				case INNER_VAR:
  					/* get the tuple from the inner node */
! 					opcode = EEOP_ASSIGN_INNER_VAR;
  					break;
  
  				case OUTER_VAR:
  					/* get the tuple from the outer node */
! 					opcode = EEOP_ASSIGN_OUTER_VAR;
  					break;
  
  					/* INDEX_VAR is handled by default case */
  
  				default:
  					/* get the tuple from the relation being scanned */
! 					opcode = EEOP_ASSIGN_SCAN_VAR;
  					break;
  			}
  
! 			scratch = (ExprEvalStep_assign_var *)
! 				ExprEvalPushStep(state,
! 			                     opcode,
! 								 NULL,
! 								 NULL);
! 			scratch->attnum = attnum - 1;
! 			scratch->resultnum = tle->resno - 1;
  		}
  		else
  		{
+ 			ExprEvalStep_assign_tmp *scratch;
+ 			ExprEvalOp opcode;
+ 
  			/*
  			 * Otherwise, compile the column expression normally.
  			 *
***************
*** 406,421 **** ExecBuildProjectionInfo(List *targetList,
  			 * force value to R/O - but only if it could be an expanded datum.
  			 */
  			if (get_typlen(exprType((Node *) tle->expr)) == -1)
! 				scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO;
  			else
! 				scratch.opcode = EEOP_ASSIGN_TMP;
! 			scratch.d.assign_tmp.resultnum = tle->resno - 1;
! 			ExprEvalPushStep(state, &scratch);
  		}
  	}
  
! 	scratch.opcode = EEOP_DONE;
! 	ExprEvalPushStep(state, &scratch);
  
  	ExecReadyExpr(state);
  
--- 493,511 ----
  			 * force value to R/O - but only if it could be an expanded datum.
  			 */
  			if (get_typlen(exprType((Node *) tle->expr)) == -1)
! 				opcode = EEOP_ASSIGN_TMP_MAKE_RO;
  			else
! 				opcode = EEOP_ASSIGN_TMP;
! 			scratch = (ExprEvalStep_assign_tmp *)
! 				ExprEvalPushStep(state,
! 			                     opcode,
! 								 NULL,
! 								 NULL);
! 			scratch->resultnum = tle->resno - 1;
  		}
  	}
  
! 	ExprEvalPushStep(state, EEOP_DONE, NULL, NULL);
  
  	ExecReadyExpr(state);
  
***************
*** 589,691 **** static void
  ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				Datum *resv, bool *resnull)
  {
! 	ExprEvalStep scratch;
  
  	/* Guard against stack overflow due to overly complex expressions */
  	check_stack_depth();
  
  	/* Step's output location is always what the caller gave us */
  	Assert(resv != NULL && resnull != NULL);
- 	scratch.resvalue = resv;
- 	scratch.resnull = resnull;
  
  	/* cases should be ordered as they are in enum NodeTag */
  	switch (nodeTag(node))
  	{
  		case T_Var:
  			{
  				Var		   *variable = (Var *) node;
  
  				if (variable->varattno == InvalidAttrNumber)
  				{
  					/* whole-row Var */
! 					ExecInitWholeRowVar(&scratch, variable, parent);
  				}
  				else if (variable->varattno <= 0)
  				{
  					/* system column */
- 					scratch.d.var.attnum = variable->varattno;
- 					scratch.d.var.vartype = variable->vartype;
  					switch (variable->varno)
  					{
  						case INNER_VAR:
! 							scratch.opcode = EEOP_INNER_SYSVAR;
  							break;
  						case OUTER_VAR:
! 							scratch.opcode = EEOP_OUTER_SYSVAR;
  							break;
  
  							/* INDEX_VAR is handled by default case */
  
  						default:
! 							scratch.opcode = EEOP_SCAN_SYSVAR;
  							break;
  					}
  				}
  				else
  				{
  					/* regular user column */
- 					scratch.d.var.attnum = variable->varattno - 1;
- 					scratch.d.var.vartype = variable->vartype;
  					/* select EEOP_*_FIRST opcode to force one-time checks */
  					switch (variable->varno)
  					{
  						case INNER_VAR:
! 							scratch.opcode = EEOP_INNER_VAR_FIRST;
  							break;
  						case OUTER_VAR:
! 							scratch.opcode = EEOP_OUTER_VAR_FIRST;
  							break;
  
  							/* INDEX_VAR is handled by default case */
  
  						default:
! 							scratch.opcode = EEOP_SCAN_VAR_FIRST;
  							break;
  					}
  				}
- 
- 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_Const:
  			{
  				Const	   *con = (Const *) node;
  
! 				scratch.opcode = EEOP_CONST;
! 				scratch.d.constval.value = con->constvalue;
! 				scratch.d.constval.isnull = con->constisnull;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_Param:
  			{
  				Param	   *param = (Param *) node;
  
  				switch (param->paramkind)
  				{
  					case PARAM_EXEC:
! 						scratch.opcode = EEOP_PARAM_EXEC;
! 						scratch.d.param.paramid = param->paramid;
! 						scratch.d.param.paramtype = param->paramtype;
  						break;
  					case PARAM_EXTERN:
! 						scratch.opcode = EEOP_PARAM_EXTERN;
! 						scratch.d.param.paramid = param->paramid;
! 						scratch.d.param.paramtype = param->paramtype;
  						break;
  					default:
  						elog(ERROR, "unrecognized paramkind: %d",
--- 679,789 ----
  ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				Datum *resv, bool *resnull)
  {
! 	ExprEvalOp opcode;
  
  	/* Guard against stack overflow due to overly complex expressions */
  	check_stack_depth();
  
  	/* Step's output location is always what the caller gave us */
  	Assert(resv != NULL && resnull != NULL);
  
  	/* cases should be ordered as they are in enum NodeTag */
  	switch (nodeTag(node))
  	{
  		case T_Var:
  			{
+ 				ExprEvalStep_var *scratch;
  				Var		   *variable = (Var *) node;
  
  				if (variable->varattno == InvalidAttrNumber)
  				{
  					/* whole-row Var */
! 					ExecInitWholeRowVar(state, variable, parent, resv, resnull);
  				}
  				else if (variable->varattno <= 0)
  				{
  					/* system column */
  					switch (variable->varno)
  					{
  						case INNER_VAR:
! 							opcode = EEOP_INNER_SYSVAR;
  							break;
  						case OUTER_VAR:
! 							opcode = EEOP_OUTER_SYSVAR;
  							break;
  
  							/* INDEX_VAR is handled by default case */
  
  						default:
! 							opcode = EEOP_SCAN_SYSVAR;
  							break;
  					}
+ 
+ 					scratch = (ExprEvalStep_var *)
+ 						ExprEvalPushStep(state,
+ 					                     opcode,
+ 										 resv,
+ 										 resnull);
+ 					scratch->attnum = variable->varattno;
+ 					scratch->vartype = variable->vartype;
  				}
  				else
  				{
  					/* regular user column */
  					/* select EEOP_*_FIRST opcode to force one-time checks */
  					switch (variable->varno)
  					{
  						case INNER_VAR:
! 							opcode = EEOP_INNER_VAR_FIRST;
  							break;
  						case OUTER_VAR:
! 							opcode = EEOP_OUTER_VAR_FIRST;
  							break;
  
  							/* INDEX_VAR is handled by default case */
  
  						default:
! 							opcode = EEOP_SCAN_VAR_FIRST;
  							break;
  					}
+ 					scratch = (ExprEvalStep_var *)
+ 						ExprEvalPushStep(state,
+ 					                     opcode,
+ 										 resv,
+ 										 resnull);
+ 					scratch->attnum = variable->varattno - 1;
+ 					scratch->vartype = variable->vartype;
  				}
  				break;
  			}
  
  		case T_Const:
  			{
+ 				ExprEvalStep_constval *scratch;
  				Const	   *con = (Const *) node;
  
! 				scratch = (ExprEvalStep_constval *)
! 					ExprEvalPushStep(state,
! 				                     EEOP_CONST,
! 									 resv,
! 									 resnull);
! 				scratch->value = con->constvalue;
! 				scratch->isnull = con->constisnull;
  				break;
  			}
  
  		case T_Param:
  			{
+ 				ExprEvalStep_param *scratch;
  				Param	   *param = (Param *) node;
  
  				switch (param->paramkind)
  				{
  					case PARAM_EXEC:
! 						opcode = EEOP_PARAM_EXEC;
  						break;
  					case PARAM_EXTERN:
! 						opcode = EEOP_PARAM_EXTERN;
  						break;
  					default:
  						elog(ERROR, "unrecognized paramkind: %d",
***************
*** 693,709 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  						break;
  				}
  
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_Aggref:
  			{
  				Aggref	   *aggref = (Aggref *) node;
  				AggrefExprState *astate = makeNode(AggrefExprState);
  
! 				scratch.opcode = EEOP_AGGREF;
! 				scratch.d.aggref.astate = astate;
  				astate->aggref = aggref;
  
  				if (parent && IsA(parent, AggState))
--- 791,818 ----
  						break;
  				}
  
! 				scratch = (ExprEvalStep_param *)
! 					ExprEvalPushStep(state,
! 				                     opcode,
! 									 resv,
! 									 resnull);
! 				scratch->paramid = param->paramid;
! 				scratch->paramtype = param->paramtype;
  				break;
  			}
  
  		case T_Aggref:
  			{
+ 				ExprEvalStep_aggref *scratch;
  				Aggref	   *aggref = (Aggref *) node;
  				AggrefExprState *astate = makeNode(AggrefExprState);
  
! 				scratch = (ExprEvalStep_aggref *)
! 					ExprEvalPushStep(state,
! 				                     EEOP_AGGREF,
! 									 resv,
! 									 resnull);
! 				scratch->astate = astate;
  				astate->aggref = aggref;
  
  				if (parent && IsA(parent, AggState))
***************
*** 718,730 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					/* planner messed up */
  					elog(ERROR, "Aggref found in non-Agg plan node");
  				}
- 
- 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_GroupingFunc:
  			{
  				GroupingFunc *grp_node = (GroupingFunc *) node;
  				Agg		   *agg;
  
--- 827,838 ----
  					/* planner messed up */
  					elog(ERROR, "Aggref found in non-Agg plan node");
  				}
  				break;
  			}
  
  		case T_GroupingFunc:
  			{
+ 				ExprEvalStep_grouping_func *scratch;
  				GroupingFunc *grp_node = (GroupingFunc *) node;
  				Agg		   *agg;
  
***************
*** 732,753 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					!IsA(parent->plan, Agg))
  					elog(ERROR, "GroupingFunc found in non-Agg plan node");
  
! 				scratch.opcode = EEOP_GROUPING_FUNC;
! 				scratch.d.grouping_func.parent = (AggState *) parent;
  
  				agg = (Agg *) (parent->plan);
  
  				if (agg->groupingSets)
! 					scratch.d.grouping_func.clauses = grp_node->cols;
  				else
! 					scratch.d.grouping_func.clauses = NIL;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_WindowFunc:
  			{
  				WindowFunc *wfunc = (WindowFunc *) node;
  				WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
  
--- 840,864 ----
  					!IsA(parent->plan, Agg))
  					elog(ERROR, "GroupingFunc found in non-Agg plan node");
  
! 				scratch = (ExprEvalStep_grouping_func *)
! 					ExprEvalPushStep(state,
! 				                     EEOP_GROUPING_FUNC,
! 									 resv,
! 									 resnull);
! 				scratch->parent = (AggState *) parent;
  
  				agg = (Agg *) (parent->plan);
  
  				if (agg->groupingSets)
! 					scratch->clauses = grp_node->cols;
  				else
! 					scratch->clauses = NIL;
  				break;
  			}
  
  		case T_WindowFunc:
  			{
+ 				ExprEvalStep_window_func *scratch;
  				WindowFunc *wfunc = (WindowFunc *) node;
  				WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
  
***************
*** 785,793 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
  				}
  
! 				scratch.opcode = EEOP_WINDOW_FUNC;
! 				scratch.d.window_func.wfstate = wfstate;
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 896,907 ----
  					elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
  				}
  
! 				scratch = (ExprEvalStep_window_func *)
! 					ExprEvalPushStep(state,
! 				                     EEOP_WINDOW_FUNC,
! 									 resv,
! 									 resnull);
! 				scratch->wfstate = wfstate;
  				break;
  			}
  
***************
*** 795,801 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  			{
  				ArrayRef   *aref = (ArrayRef *) node;
  
! 				ExecInitArrayRef(&scratch, aref, parent, state, resv, resnull);
  				break;
  			}
  
--- 909,915 ----
  			{
  				ArrayRef   *aref = (ArrayRef *) node;
  
! 				ExecInitArrayRef(aref, parent, state, resv, resnull);
  				break;
  			}
  
***************
*** 803,812 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  			{
  				FuncExpr   *func = (FuncExpr *) node;
  
! 				ExecInitFunc(&scratch, node,
  							 func->args, func->funcid, func->inputcollid,
! 							 parent, state);
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 917,925 ----
  			{
  				FuncExpr   *func = (FuncExpr *) node;
  
! 				ExecInitFunc(node,
  							 func->args, func->funcid, func->inputcollid,
! 							 parent, state, resv, resnull);
  				break;
  			}
  
***************
*** 814,823 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  			{
  				OpExpr	   *op = (OpExpr *) node;
  
! 				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state);
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 927,935 ----
  			{
  				OpExpr	   *op = (OpExpr *) node;
  
! 				ExecInitFunc(node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state, resv, resnull);
  				break;
  			}
  
***************
*** 825,833 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  			{
  				DistinctExpr *op = (DistinctExpr *) node;
  
! 				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state);
  
  				/*
  				 * Change opcode of call instruction to EEOP_DISTINCT.
--- 937,945 ----
  			{
  				DistinctExpr *op = (DistinctExpr *) node;
  
! 				ExecInitFunc(node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state, resv, resnull);
  
  				/*
  				 * Change opcode of call instruction to EEOP_DISTINCT.
***************
*** 838,845 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * we decided to do that here, we'd probably want separate
  				 * opcodes for FUSAGE or not.
  				 */
! 				scratch.opcode = EEOP_DISTINCT;
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 950,956 ----
  				 * we decided to do that here, we'd probably want separate
  				 * opcodes for FUSAGE or not.
  				 */
! 				state->last_step->opcode = EEOP_DISTINCT;
  				break;
  			}
  
***************
*** 847,855 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  			{
  				NullIfExpr *op = (NullIfExpr *) node;
  
! 				ExecInitFunc(&scratch, node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state);
  
  				/*
  				 * Change opcode of call instruction to EEOP_NULLIF.
--- 958,966 ----
  			{
  				NullIfExpr *op = (NullIfExpr *) node;
  
! 				ExecInitFunc(node,
  							 op->args, op->opfuncid, op->inputcollid,
! 							 parent, state, resv, resnull);
  
  				/*
  				 * Change opcode of call instruction to EEOP_NULLIF.
***************
*** 860,872 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * we decided to do that here, we'd probably want separate
  				 * opcodes for FUSAGE or not.
  				 */
! 				scratch.opcode = EEOP_NULLIF;
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_ScalarArrayOpExpr:
  			{
  				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
  				Expr	   *scalararg;
  				Expr	   *arrayarg;
--- 971,983 ----
  				 * we decided to do that here, we'd probably want separate
  				 * opcodes for FUSAGE or not.
  				 */
! 				state->last_step->opcode = EEOP_NULLIF;
  				break;
  			}
  
  		case T_ScalarArrayOpExpr:
  			{
+ 				ExprEvalStep_scalararrayop *scratch;
  				ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
  				Expr	   *scalararg;
  				Expr	   *arrayarg;
***************
*** 908,934 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				ExecInitExprRec(arrayarg, parent, state, resv, resnull);
  
  				/* And perform the operation */
! 				scratch.opcode = EEOP_SCALARARRAYOP;
! 				scratch.d.scalararrayop.element_type = InvalidOid;
! 				scratch.d.scalararrayop.useOr = opexpr->useOr;
! 				scratch.d.scalararrayop.finfo = finfo;
! 				scratch.d.scalararrayop.fcinfo_data = fcinfo;
! 				scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_BoolExpr:
  			{
  				BoolExpr   *boolexpr = (BoolExpr *) node;
  				int			nargs = list_length(boolexpr->args);
  				List	   *adjust_jumps = NIL;
  				int			off;
  				ListCell   *lc;
  
  				/* allocate scratch memory used by all steps of AND/OR */
  				if (boolexpr->boolop != NOT_EXPR)
! 					scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool));
  
  				/*
  				 * For each argument evaluate the argument itself, then
--- 1019,1050 ----
  				ExecInitExprRec(arrayarg, parent, state, resv, resnull);
  
  				/* And perform the operation */
! 				scratch = (ExprEvalStep_scalararrayop *)
! 					ExprEvalPushStep(state,
! 				                     EEOP_SCALARARRAYOP,
! 									 resv,
! 									 resnull);
! 				scratch->element_type = InvalidOid;
! 				scratch->useOr = opexpr->useOr;
! 				scratch->finfo = finfo;
! 				scratch->fcinfo_data = fcinfo;
! 				scratch->fn_addr = finfo->fn_addr;
  				break;
  			}
  
  		case T_BoolExpr:
  			{
+ 				ExprEvalStep_boolexpr *scratch;
  				BoolExpr   *boolexpr = (BoolExpr *) node;
  				int			nargs = list_length(boolexpr->args);
  				List	   *adjust_jumps = NIL;
  				int			off;
  				ListCell   *lc;
+ 				bool       *anynull = NULL;
  
  				/* allocate scratch memory used by all steps of AND/OR */
  				if (boolexpr->boolop != NOT_EXPR)
! 					anynull = (bool *) palloc(sizeof(bool));
  
  				/*
  				 * For each argument evaluate the argument itself, then
***************
*** 958,983 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  							Assert(nargs >= 2);
  
  							if (off == 0)
! 								scratch.opcode = EEOP_BOOL_AND_STEP_FIRST;
  							else if (off + 1 == nargs)
! 								scratch.opcode = EEOP_BOOL_AND_STEP_LAST;
  							else
! 								scratch.opcode = EEOP_BOOL_AND_STEP;
  							break;
  						case OR_EXPR:
  							Assert(nargs >= 2);
  
  							if (off == 0)
! 								scratch.opcode = EEOP_BOOL_OR_STEP_FIRST;
  							else if (off + 1 == nargs)
! 								scratch.opcode = EEOP_BOOL_OR_STEP_LAST;
  							else
! 								scratch.opcode = EEOP_BOOL_OR_STEP;
  							break;
  						case NOT_EXPR:
  							Assert(nargs == 1);
  
! 							scratch.opcode = EEOP_BOOL_NOT_STEP;
  							break;
  						default:
  							elog(ERROR, "unrecognized boolop: %d",
--- 1074,1099 ----
  							Assert(nargs >= 2);
  
  							if (off == 0)
! 								opcode = EEOP_BOOL_AND_STEP_FIRST;
  							else if (off + 1 == nargs)
! 								opcode = EEOP_BOOL_AND_STEP_LAST;
  							else
! 								opcode = EEOP_BOOL_AND_STEP;
  							break;
  						case OR_EXPR:
  							Assert(nargs >= 2);
  
  							if (off == 0)
! 								opcode = EEOP_BOOL_OR_STEP_FIRST;
  							else if (off + 1 == nargs)
! 								opcode = EEOP_BOOL_OR_STEP_LAST;
  							else
! 								opcode = EEOP_BOOL_OR_STEP;
  							break;
  						case NOT_EXPR:
  							Assert(nargs == 1);
  
! 							opcode = EEOP_BOOL_NOT_STEP;
  							break;
  						default:
  							elog(ERROR, "unrecognized boolop: %d",
***************
*** 985,1011 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  							break;
  					}
  
! 					scratch.d.boolexpr.jumpdone = -1;
! 					ExprEvalPushStep(state, &scratch);
! 					adjust_jumps = lappend_int(adjust_jumps,
! 											   state->steps_len - 1);
  					off++;
  				}
  
! 				/* adjust jump targets */
! 				foreach(lc, adjust_jumps)
! 				{
! 					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
! 
! 					Assert(as->d.boolexpr.jumpdone == -1);
! 					as->d.boolexpr.jumpdone = state->steps_len;
! 				}
  
  				break;
  			}
  
  		case T_SubPlan:
  			{
  				SubPlan    *subplan = (SubPlan *) node;
  				SubPlanState *sstate;
  
--- 1101,1126 ----
  							break;
  					}
  
! 					scratch = (ExprEvalStep_boolexpr *)
! 						ExprEvalPushStep(state,
! 				                         opcode,
! 										 resv,
! 										 resnull);
! 					scratch->jumpdone = NULL;
! 					scratch->anynull = anynull;
! 					adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  					off++;
  				}
  
! 				/* Resolve the jumpdone pointers when the next operator is created */
! 				state->adjust_jumps = adjust_jumps;
  
  				break;
  			}
  
  		case T_SubPlan:
  			{
+ 				ExprEvalStep_subplan *scratch;
  				SubPlan    *subplan = (SubPlan *) node;
  				SubPlanState *sstate;
  
***************
*** 1017,1031 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				/* add SubPlanState nodes to parent->subPlan */
  				parent->subPlan = lappend(parent->subPlan, sstate);
  
! 				scratch.opcode = EEOP_SUBPLAN;
! 				scratch.d.subplan.sstate = sstate;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_AlternativeSubPlan:
  			{
  				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
  				AlternativeSubPlanState *asstate;
  
--- 1132,1149 ----
  				/* add SubPlanState nodes to parent->subPlan */
  				parent->subPlan = lappend(parent->subPlan, sstate);
  
! 				scratch = (ExprEvalStep_subplan *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_SUBPLAN,
! 									 resv,
! 									 resnull);
! 				scratch->sstate = sstate;
  				break;
  			}
  
  		case T_AlternativeSubPlan:
  			{
+ 				ExprEvalStep_alternative_subplan *scratch;
  				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
  				AlternativeSubPlanState *asstate;
  
***************
*** 1034,1065 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  
  				asstate = ExecInitAlternativeSubPlan(asplan, parent);
  
! 				scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
! 				scratch.d.alternative_subplan.asstate = asstate;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_FieldSelect:
  			{
  				FieldSelect *fselect = (FieldSelect *) node;
  
  				/* evaluate row/record argument into result area */
  				ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
  
  				/* and extract field */
! 				scratch.opcode = EEOP_FIELDSELECT;
! 				scratch.d.fieldselect.fieldnum = fselect->fieldnum;
! 				scratch.d.fieldselect.resulttype = fselect->resulttype;
! 				scratch.d.fieldselect.argdesc = NULL;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_FieldStore:
  			{
  				FieldStore *fstore = (FieldStore *) node;
  				TupleDesc	tupDesc;
  				TupleDesc  *descp;
--- 1152,1189 ----
  
  				asstate = ExecInitAlternativeSubPlan(asplan, parent);
  
! 				scratch = (ExprEvalStep_alternative_subplan *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_ALTERNATIVE_SUBPLAN,
! 									 resv,
! 									 resnull);
! 				scratch->asstate = asstate;
  				break;
  			}
  
  		case T_FieldSelect:
  			{
+ 				ExprEvalStep_fieldselect *scratch;
  				FieldSelect *fselect = (FieldSelect *) node;
  
  				/* evaluate row/record argument into result area */
  				ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
  
  				/* and extract field */
! 				scratch = (ExprEvalStep_fieldselect *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_FIELDSELECT,
! 									 resv,
! 									 resnull);
! 				scratch->fieldnum = fselect->fieldnum;
! 				scratch->resulttype = fselect->resulttype;
! 				scratch->argdesc = NULL;
  				break;
  			}
  
  		case T_FieldStore:
  			{
+ 				ExprEvalStep_fieldstore *scratch;
  				FieldStore *fstore = (FieldStore *) node;
  				TupleDesc	tupDesc;
  				TupleDesc  *descp;
***************
*** 1086,1098 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
  
  				/* next, deform the input tuple into our workspace */
! 				scratch.opcode = EEOP_FIELDSTORE_DEFORM;
! 				scratch.d.fieldstore.fstore = fstore;
! 				scratch.d.fieldstore.argdesc = descp;
! 				scratch.d.fieldstore.values = values;
! 				scratch.d.fieldstore.nulls = nulls;
! 				scratch.d.fieldstore.ncolumns = ncolumns;
! 				ExprEvalPushStep(state, &scratch);
  
  				/* evaluate new field values, store in workspace columns */
  				forboth(l1, fstore->newvals, l2, fstore->fieldnums)
--- 1210,1225 ----
  				ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
  
  				/* next, deform the input tuple into our workspace */
! 				scratch = (ExprEvalStep_fieldstore *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_FIELDSTORE_DEFORM,
! 									 resv,
! 									 resnull);
! 				scratch->fstore = fstore;
! 				scratch->argdesc = descp;
! 				scratch->values = values;
! 				scratch->nulls = nulls;
! 				scratch->ncolumns = ncolumns;
  
  				/* evaluate new field values, store in workspace columns */
  				forboth(l1, fstore->newvals, l2, fstore->fieldnums)
***************
*** 1143,1155 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				}
  
  				/* finally, form result tuple */
! 				scratch.opcode = EEOP_FIELDSTORE_FORM;
! 				scratch.d.fieldstore.fstore = fstore;
! 				scratch.d.fieldstore.argdesc = descp;
! 				scratch.d.fieldstore.values = values;
! 				scratch.d.fieldstore.nulls = nulls;
! 				scratch.d.fieldstore.ncolumns = ncolumns;
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 1270,1285 ----
  				}
  
  				/* finally, form result tuple */
! 				scratch = (ExprEvalStep_fieldstore *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_FIELDSTORE_FORM,
! 									 resv,
! 									 resnull);
! 				scratch->fstore = fstore;
! 				scratch->argdesc = descp;
! 				scratch->values = values;
! 				scratch->nulls = nulls;
! 				scratch->ncolumns = ncolumns;
  				break;
  			}
  
***************
*** 1164,1169 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
--- 1294,1300 ----
  
  		case T_CoerceViaIO:
  			{
+ 				ExprEvalStep_iocoerce *scratch;
  				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
  				Oid			iofunc;
  				bool		typisvarlena;
***************
*** 1181,1228 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * We don't check permissions here as a type's input/output
  				 * function are assumed to be executable by everyone.
  				 */
! 				scratch.opcode = EEOP_IOCOERCE;
  
  				/* lookup the source type's output function */
! 				scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo));
! 				scratch.d.iocoerce.fcinfo_data_out = palloc0(sizeof(FunctionCallInfoData));
  
  				getTypeOutputInfo(exprType((Node *) iocoerce->arg),
  								  &iofunc, &typisvarlena);
! 				fmgr_info(iofunc, scratch.d.iocoerce.finfo_out);
! 				fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_out);
! 				InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_out,
! 										 scratch.d.iocoerce.finfo_out,
  										 1, InvalidOid, NULL, NULL);
  
  				/* lookup the result type's input function */
! 				scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo));
! 				scratch.d.iocoerce.fcinfo_data_in = palloc0(sizeof(FunctionCallInfoData));
  
  				getTypeInputInfo(iocoerce->resulttype,
  								 &iofunc, &typioparam);
! 				fmgr_info(iofunc, scratch.d.iocoerce.finfo_in);
! 				fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in);
! 				InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in,
! 										 scratch.d.iocoerce.finfo_in,
  										 3, InvalidOid, NULL, NULL);
  
  				/*
  				 * We can preload the second and third arguments for the input
  				 * function, since they're constants.
  				 */
! 				fcinfo_in = scratch.d.iocoerce.fcinfo_data_in;
  				fcinfo_in->arg[1] = ObjectIdGetDatum(typioparam);
  				fcinfo_in->argnull[1] = false;
  				fcinfo_in->arg[2] = Int32GetDatum(-1);
  				fcinfo_in->argnull[2] = false;
- 
- 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_ArrayCoerceExpr:
  			{
  				ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
  				Oid			resultelemtype;
  
--- 1312,1362 ----
  				 * We don't check permissions here as a type's input/output
  				 * function are assumed to be executable by everyone.
  				 */
! 				scratch = (ExprEvalStep_iocoerce *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_IOCOERCE,
! 									 resv,
! 									 resnull);
  
  				/* lookup the source type's output function */
! 				scratch->finfo_out = palloc0(sizeof(FmgrInfo));
! 				scratch->fcinfo_data_out = palloc0(sizeof(FunctionCallInfoData));
  
  				getTypeOutputInfo(exprType((Node *) iocoerce->arg),
  								  &iofunc, &typisvarlena);
! 				fmgr_info(iofunc, scratch->finfo_out);
! 				fmgr_info_set_expr((Node *) node, scratch->finfo_out);
! 				InitFunctionCallInfoData(*scratch->fcinfo_data_out,
! 										 scratch->finfo_out,
  										 1, InvalidOid, NULL, NULL);
  
  				/* lookup the result type's input function */
! 				scratch->finfo_in = palloc0(sizeof(FmgrInfo));
! 				scratch->fcinfo_data_in = palloc0(sizeof(FunctionCallInfoData));
  
  				getTypeInputInfo(iocoerce->resulttype,
  								 &iofunc, &typioparam);
! 				fmgr_info(iofunc, scratch->finfo_in);
! 				fmgr_info_set_expr((Node *) node, scratch->finfo_in);
! 				InitFunctionCallInfoData(*scratch->fcinfo_data_in,
! 										 scratch->finfo_in,
  										 3, InvalidOid, NULL, NULL);
  
  				/*
  				 * We can preload the second and third arguments for the input
  				 * function, since they're constants.
  				 */
! 				fcinfo_in = scratch->fcinfo_data_in;
  				fcinfo_in->arg[1] = ObjectIdGetDatum(typioparam);
  				fcinfo_in->argnull[1] = false;
  				fcinfo_in->arg[2] = Int32GetDatum(-1);
  				fcinfo_in->argnull[2] = false;
  				break;
  			}
  
  		case T_ArrayCoerceExpr:
  			{
+ 				ExprEvalStep_arraycoerce *scratch;
  				ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
  				Oid			resultelemtype;
  
***************
*** 1237,1245 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				/* Arrays over domains aren't supported yet */
  				Assert(getBaseType(resultelemtype) == resultelemtype);
  
! 				scratch.opcode = EEOP_ARRAYCOERCE;
! 				scratch.d.arraycoerce.coerceexpr = acoerce;
! 				scratch.d.arraycoerce.resultelemtype = resultelemtype;
  
  				if (OidIsValid(acoerce->elemfuncid))
  				{
--- 1371,1383 ----
  				/* Arrays over domains aren't supported yet */
  				Assert(getBaseType(resultelemtype) == resultelemtype);
  
! 				scratch = (ExprEvalStep_arraycoerce *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_ARRAYCOERCE,
! 									 resv,
! 									 resnull);
! 				scratch->coerceexpr = acoerce;
! 				scratch->resultelemtype = resultelemtype;
  
  				if (OidIsValid(acoerce->elemfuncid))
  				{
***************
*** 1255,1298 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					InvokeFunctionExecuteHook(acoerce->elemfuncid);
  
  					/* Set up the primary fmgr lookup information */
! 					scratch.d.arraycoerce.elemfunc =
  						(FmgrInfo *) palloc0(sizeof(FmgrInfo));
  					fmgr_info(acoerce->elemfuncid,
! 							  scratch.d.arraycoerce.elemfunc);
  					fmgr_info_set_expr((Node *) acoerce,
! 									   scratch.d.arraycoerce.elemfunc);
  
  					/* Set up workspace for array_map */
! 					scratch.d.arraycoerce.amstate =
  						(ArrayMapState *) palloc0(sizeof(ArrayMapState));
  				}
  				else
  				{
  					/* Don't need workspace if there's no conversion func */
! 					scratch.d.arraycoerce.elemfunc = NULL;
! 					scratch.d.arraycoerce.amstate = NULL;
  				}
- 
- 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_ConvertRowtypeExpr:
  			{
  				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
  
  				/* evaluate argument into step's result area */
  				ExecInitExprRec(convert->arg, parent, state, resv, resnull);
  
  				/* and push conversion step */
! 				scratch.opcode = EEOP_CONVERT_ROWTYPE;
! 				scratch.d.convert_rowtype.convert = convert;
! 				scratch.d.convert_rowtype.indesc = NULL;
! 				scratch.d.convert_rowtype.outdesc = NULL;
! 				scratch.d.convert_rowtype.map = NULL;
! 				scratch.d.convert_rowtype.initialized = false;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 1393,1437 ----
  					InvokeFunctionExecuteHook(acoerce->elemfuncid);
  
  					/* Set up the primary fmgr lookup information */
! 					scratch->elemfunc =
  						(FmgrInfo *) palloc0(sizeof(FmgrInfo));
  					fmgr_info(acoerce->elemfuncid,
! 							  scratch->elemfunc);
  					fmgr_info_set_expr((Node *) acoerce,
! 									   scratch->elemfunc);
  
  					/* Set up workspace for array_map */
! 					scratch->amstate =
  						(ArrayMapState *) palloc0(sizeof(ArrayMapState));
  				}
  				else
  				{
  					/* Don't need workspace if there's no conversion func */
! 					scratch->elemfunc = NULL;
! 					scratch->amstate = NULL;
  				}
  				break;
  			}
  
  		case T_ConvertRowtypeExpr:
  			{
+ 				ExprEvalStep_convert_rowtype *scratch;
  				ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
  
  				/* evaluate argument into step's result area */
  				ExecInitExprRec(convert->arg, parent, state, resv, resnull);
  
  				/* and push conversion step */
! 				scratch = (ExprEvalStep_convert_rowtype *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_CONVERT_ROWTYPE,
! 									 resv,
! 									 resnull);
! 				scratch->convert = convert;
! 				scratch->indesc = NULL;
! 				scratch->outdesc = NULL;
! 				scratch->map = NULL;
! 				scratch->initialized = false;
  				break;
  			}
  
***************
*** 1325,1340 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					 */
  					if (get_typlen(exprType((Node *) caseExpr->arg)) == -1)
  					{
  						/* change caseval in-place */
! 						scratch.opcode = EEOP_MAKE_READONLY;
! 						scratch.resvalue = caseval;
! 						scratch.resnull = casenull;
! 						scratch.d.make_readonly.value = caseval;
! 						scratch.d.make_readonly.isnull = casenull;
! 						ExprEvalPushStep(state, &scratch);
! 						/* restore normal settings of scratch fields */
! 						scratch.resvalue = resv;
! 						scratch.resnull = resnull;
  					}
  				}
  
--- 1464,1479 ----
  					 */
  					if (get_typlen(exprType((Node *) caseExpr->arg)) == -1)
  					{
+ 						ExprEvalStep_make_readonly *scratch;
+ 
  						/* change caseval in-place */
! 						scratch = (ExprEvalStep_make_readonly *)
! 							ExprEvalPushStep(state,
! 					                         EEOP_MAKE_READONLY,
! 											 caseval,
! 											 casenull);
! 						scratch->value = caseval;
! 						scratch->isnull = casenull;
  					}
  				}
  
***************
*** 1346,1355 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 */
  				foreach(lc, caseExpr->args)
  				{
  					CaseWhen   *when = (CaseWhen *) lfirst(lc);
  					Datum	   *save_innermost_caseval;
  					bool	   *save_innermost_casenull;
! 					int			whenstep;
  
  					/*
  					 * Make testexpr result available to CaseTestExpr nodes
--- 1485,1495 ----
  				 */
  				foreach(lc, caseExpr->args)
  				{
+ 					ExprEvalStep_jump *scratch;
  					CaseWhen   *when = (CaseWhen *) lfirst(lc);
  					Datum	   *save_innermost_caseval;
  					bool	   *save_innermost_casenull;
! 					ExprEvalStep_jump *whenstep;
  
  					/*
  					 * Make testexpr result available to CaseTestExpr nodes
***************
*** 1373,1382 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					state->innermost_casenull = save_innermost_casenull;
  
  					/* If WHEN result isn't true, jump to next CASE arm */
! 					scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
! 					scratch.d.jump.jumpdone = -1;	/* computed later */
! 					ExprEvalPushStep(state, &scratch);
! 					whenstep = state->steps_len - 1;
  
  					/*
  					 * If WHEN result is true, evaluate THEN result, storing
--- 1513,1525 ----
  					state->innermost_casenull = save_innermost_casenull;
  
  					/* If WHEN result isn't true, jump to next CASE arm */
! 					scratch = (ExprEvalStep_jump *)
! 						ExprEvalPushStep(state,
! 				                         EEOP_JUMP_IF_NOT_TRUE,
! 										 resv,
! 										 resnull);
! 					scratch->jumpdone = NULL;		/* computed later */
! 					whenstep = scratch;
  
  					/*
  					 * If WHEN result is true, evaluate THEN result, storing
***************
*** 1385,1406 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					ExecInitExprRec(when->result, parent, state, resv, resnull);
  
  					/* Emit JUMP step to jump to end of CASE's code */
! 					scratch.opcode = EEOP_JUMP;
! 					scratch.d.jump.jumpdone = -1;	/* computed later */
! 					ExprEvalPushStep(state, &scratch);
  
  					/*
  					 * Don't know address for that jump yet, compute once the
  					 * whole CASE expression is built.
  					 */
! 					adjust_jumps = lappend_int(adjust_jumps,
! 											   state->steps_len - 1);
  
  					/*
  					 * But we can set WHEN test's jump target now, to make it
  					 * jump to the next WHEN subexpression or the ELSE.
  					 */
! 					state->steps[whenstep].d.jump.jumpdone = state->steps_len;
  				}
  
  				/* transformCaseExpr always adds a default */
--- 1528,1554 ----
  					ExecInitExprRec(when->result, parent, state, resv, resnull);
  
  					/* Emit JUMP step to jump to end of CASE's code */
! 					scratch = (ExprEvalStep_jump *)
! 						ExprEvalPushStep(state,
! 				                         EEOP_JUMP,
! 										 resv,
! 										 resnull);
! 					scratch->jumpdone = NULL;		/* computed later */
  
  					/*
  					 * Don't know address for that jump yet, compute once the
  					 * whole CASE expression is built.
  					 */
! 					adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  
  					/*
  					 * But we can set WHEN test's jump target now, to make it
  					 * jump to the next WHEN subexpression or the ELSE.
+ 					 * Since we only have one pointer to resolve, just put it
+ 					 * directly into state->adjust_jumps.
  					 */
! 					state->adjust_jumps = lappend(state->adjust_jumps,
! 					                              &whenstep->jumpdone);
  				}
  
  				/* transformCaseExpr always adds a default */
***************
*** 1410,1430 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				ExecInitExprRec(caseExpr->defresult, parent, state,
  								resv, resnull);
  
! 				/* adjust jump targets */
! 				foreach(lc, adjust_jumps)
! 				{
! 					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
! 
! 					Assert(as->opcode == EEOP_JUMP);
! 					Assert(as->d.jump.jumpdone == -1);
! 					as->d.jump.jumpdone = state->steps_len;
! 				}
  
  				break;
  			}
  
  		case T_CaseTestExpr:
  			{
  				/*
  				 * Read from location identified by innermost_caseval.  Note
  				 * that innermost_caseval could be NULL, if this node isn't
--- 1558,1581 ----
  				ExecInitExprRec(caseExpr->defresult, parent, state,
  								resv, resnull);
  
! 				/*
! 				 * Resolve the jumpdone pointers when the next operator is created
! 				 * If it happens that the ELSE expr didn't generate any operators
! 				 * then there will still be an entry in state->adjust_jumps
! 				 * for the last whenstep. We don't want to lose it, so add it
! 				 * to the current list.
! 				 */
! 				if (state->adjust_jumps != NIL)
! 					adjust_jumps = list_concat(adjust_jumps, state->adjust_jumps);
! 				state->adjust_jumps = adjust_jumps;
  
  				break;
  			}
  
  		case T_CaseTestExpr:
  			{
+ 				ExprEvalStep_casetest *scratch;
+ 
  				/*
  				 * Read from location identified by innermost_caseval.  Note
  				 * that innermost_caseval could be NULL, if this node isn't
***************
*** 1433,1474 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * supplied in econtext->caseValue_datum.  We'll take care of
  				 * that scenario at runtime.
  				 */
! 				scratch.opcode = EEOP_CASE_TESTVAL;
! 				scratch.d.casetest.value = state->innermost_caseval;
! 				scratch.d.casetest.isnull = state->innermost_casenull;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_ArrayExpr:
  			{
  				ArrayExpr  *arrayexpr = (ArrayExpr *) node;
  				int			nelems = list_length(arrayexpr->elements);
  				ListCell   *lc;
  				int			elemoff;
  
  				/*
  				 * Evaluate by computing each element, and then forming the
  				 * array.  Elements are computed into scratch arrays
  				 * associated with the ARRAYEXPR step.
  				 */
- 				scratch.opcode = EEOP_ARRAYEXPR;
- 				scratch.d.arrayexpr.elemvalues =
- 					(Datum *) palloc(sizeof(Datum) * nelems);
- 				scratch.d.arrayexpr.elemnulls =
- 					(bool *) palloc(sizeof(bool) * nelems);
- 				scratch.d.arrayexpr.nelems = nelems;
  
! 				/* fill remaining fields of step */
! 				scratch.d.arrayexpr.multidims = arrayexpr->multidims;
! 				scratch.d.arrayexpr.elemtype = arrayexpr->element_typeid;
! 
! 				/* do one-time catalog lookup for type info */
! 				get_typlenbyvalalign(arrayexpr->element_typeid,
! 									 &scratch.d.arrayexpr.elemlength,
! 									 &scratch.d.arrayexpr.elembyval,
! 									 &scratch.d.arrayexpr.elemalign);
  
  				/* prepare to evaluate all arguments */
  				elemoff = 0;
--- 1584,1617 ----
  				 * supplied in econtext->caseValue_datum.  We'll take care of
  				 * that scenario at runtime.
  				 */
! 				scratch = (ExprEvalStep_casetest *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_CASE_TESTVAL,
! 									 resv,
! 									 resnull);
! 				scratch->value = state->innermost_caseval;
! 				scratch->isnull = state->innermost_casenull;
  				break;
  			}
  
  		case T_ArrayExpr:
  			{
+ 				ExprEvalStep_arrayexpr *scratch;
  				ArrayExpr  *arrayexpr = (ArrayExpr *) node;
  				int			nelems = list_length(arrayexpr->elements);
  				ListCell   *lc;
  				int			elemoff;
+ 				Datum      *elemvalues;
+ 				bool       *elemnulls;
  
  				/*
  				 * Evaluate by computing each element, and then forming the
  				 * array.  Elements are computed into scratch arrays
  				 * associated with the ARRAYEXPR step.
  				 */
  
! 				elemvalues = (Datum *) palloc(sizeof(Datum) * nelems);
! 				elemnulls = (bool *) palloc(sizeof(bool) * nelems);
  
  				/* prepare to evaluate all arguments */
  				elemoff = 0;
***************
*** 1477,1497 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					ExecInitExprRec(e, parent, state,
! 									&scratch.d.arrayexpr.elemvalues[elemoff],
! 									&scratch.d.arrayexpr.elemnulls[elemoff]);
  					elemoff++;
  				}
  
  				/* and then collect all into an array */
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_RowExpr:
  			{
  				RowExpr    *rowexpr = (RowExpr *) node;
  				int			nelems = list_length(rowexpr->args);
  				TupleDesc	tupdesc;
  				Form_pg_attribute *attrs;
  				int			i;
  				ListCell   *l;
--- 1620,1660 ----
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					ExecInitExprRec(e, parent, state,
! 									&elemvalues[elemoff],
! 									&elemnulls[elemoff]);
  					elemoff++;
  				}
  
  				/* and then collect all into an array */
! 				scratch = (ExprEvalStep_arrayexpr *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_ARRAYEXPR,
! 									 resv,
! 									 resnull);
! 				scratch->elemvalues = elemvalues;
! 				scratch->elemnulls = elemnulls;
! 				scratch->nelems = nelems;
! 
! 				/* fill remaining fields of step */
! 				scratch->multidims = arrayexpr->multidims;
! 				scratch->elemtype = arrayexpr->element_typeid;
! 
! 				/* do one-time catalog lookup for type info */
! 				get_typlenbyvalalign(arrayexpr->element_typeid,
! 									 &scratch->elemlength,
! 									 &scratch->elembyval,
! 									 &scratch->elemalign);
  				break;
  			}
  
  		case T_RowExpr:
  			{
+ 				ExprEvalStep_row *scratch;
  				RowExpr    *rowexpr = (RowExpr *) node;
  				int			nelems = list_length(rowexpr->args);
  				TupleDesc	tupdesc;
+ 				Datum      *elemvalues;
+ 				bool       *elemnulls;
  				Form_pg_attribute *attrs;
  				int			i;
  				ListCell   *l;
***************
*** 1527,1542 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * Evaluate by first building datums for each field, and then
  				 * a final step forming the composite datum.
  				 */
- 				scratch.opcode = EEOP_ROW;
- 				scratch.d.row.tupdesc = tupdesc;
  
  				/* space for the individual field datums */
! 				scratch.d.row.elemvalues =
! 					(Datum *) palloc(sizeof(Datum) * nelems);
! 				scratch.d.row.elemnulls =
! 					(bool *) palloc(sizeof(bool) * nelems);
  				/* as explained above, make sure any extra columns are null */
! 				memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems);
  
  				/* Set up evaluation, skipping any deleted columns */
  				attrs = tupdesc->attrs;
--- 1690,1702 ----
  				 * Evaluate by first building datums for each field, and then
  				 * a final step forming the composite datum.
  				 */
  
  				/* space for the individual field datums */
! 				elemvalues = (Datum *) palloc(sizeof(Datum) * nelems);
! 				elemnulls = (bool *) palloc(sizeof(bool) * nelems);
! 
  				/* as explained above, make sure any extra columns are null */
! 				memset(elemnulls, true, sizeof(bool) * nelems);
  
  				/* Set up evaluation, skipping any deleted columns */
  				attrs = tupdesc->attrs;
***************
*** 1572,1589 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  
  					/* Evaluate column expr into appropriate workspace slot */
  					ExecInitExprRec(e, parent, state,
! 									&scratch.d.row.elemvalues[i],
! 									&scratch.d.row.elemnulls[i]);
  					i++;
  				}
  
  				/* And finally build the row value */
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_RowCompareExpr:
  			{
  				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
  				int			nopers = list_length(rcexpr->opnos);
  				List	   *adjust_jumps = NIL;
--- 1732,1757 ----
  
  					/* Evaluate column expr into appropriate workspace slot */
  					ExecInitExprRec(e, parent, state,
! 									&elemvalues[i],
! 									&elemnulls[i]);
  					i++;
  				}
  
  				/* And finally build the row value */
! 				scratch = (ExprEvalStep_row *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_ROW,
! 									 resv,
! 									 resnull);
! 				scratch->tupdesc = tupdesc;
! 				scratch->elemvalues = elemvalues;
! 				scratch->elemnulls = elemnulls;
  				break;
  			}
  
  		case T_RowCompareExpr:
  			{
+ 				ExprEvalStep_rowcompare_final *scratch;
  				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
  				int			nopers = list_length(rcexpr->opnos);
  				List	   *adjust_jumps = NIL;
***************
*** 1620,1625 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
--- 1788,1794 ----
  					 l_opfamily = lnext(l_opfamily),
  					 l_inputcollid = lnext(l_inputcollid))
  				{
+ 					ExprEvalStep_rowcompare_step *scratch;
  					Expr	   *left_expr = (Expr *) lfirst(l_left_expr);
  					Expr	   *right_expr = (Expr *) lfirst(l_right_expr);
  					Oid			opno = lfirst_oid(l_opno);
***************
*** 1665,1681 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					ExecInitExprRec(right_expr, parent, state,
  									&fcinfo->arg[1], &fcinfo->argnull[1]);
  
! 					scratch.opcode = EEOP_ROWCOMPARE_STEP;
! 					scratch.d.rowcompare_step.finfo = finfo;
! 					scratch.d.rowcompare_step.fcinfo_data = fcinfo;
! 					scratch.d.rowcompare_step.fn_addr = finfo->fn_addr;
  					/* jump targets filled below */
! 					scratch.d.rowcompare_step.jumpnull = -1;
! 					scratch.d.rowcompare_step.jumpdone = -1;
  
! 					ExprEvalPushStep(state, &scratch);
! 					adjust_jumps = lappend_int(adjust_jumps,
! 											   state->steps_len - 1);
  				}
  
  				/*
--- 1834,1852 ----
  					ExecInitExprRec(right_expr, parent, state,
  									&fcinfo->arg[1], &fcinfo->argnull[1]);
  
! 					scratch = (ExprEvalStep_rowcompare_step *)
! 						ExprEvalPushStep(state,
! 				                         EEOP_ROWCOMPARE_STEP,
! 										 resv,
! 										 resnull);
! 					scratch->finfo = finfo;
! 					scratch->fcinfo_data = fcinfo;
! 					scratch->fn_addr = finfo->fn_addr;
  					/* jump targets filled below */
! 					scratch->jumpnull = NULL;
! 					scratch->jumpdone = NULL;
  
! 					adjust_jumps = lappend(adjust_jumps, scratch);
  				}
  
  				/*
***************
*** 1684,1713 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 */
  				if (nopers == 0)
  				{
! 					scratch.opcode = EEOP_CONST;
! 					scratch.d.constval.value = Int32GetDatum(0);
! 					scratch.d.constval.isnull = false;
! 					ExprEvalPushStep(state, &scratch);
  				}
  
  				/* Finally, examine the last comparison result */
! 				scratch.opcode = EEOP_ROWCOMPARE_FINAL;
! 				scratch.d.rowcompare_final.rctype = rcexpr->rctype;
! 				ExprEvalPushStep(state, &scratch);
  
  				/* adjust jump targetss */
  				foreach(lc, adjust_jumps)
  				{
! 					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
  
  					Assert(as->opcode == EEOP_ROWCOMPARE_STEP);
! 					Assert(as->d.rowcompare_step.jumpdone == -1);
! 					Assert(as->d.rowcompare_step.jumpnull == -1);
  
  					/* jump to comparison evaluation */
! 					as->d.rowcompare_step.jumpdone = state->steps_len - 1;
  					/* jump to the following expression */
! 					as->d.rowcompare_step.jumpnull = state->steps_len;
  				}
  
  				break;
--- 1855,1894 ----
  				 */
  				if (nopers == 0)
  				{
! 					ExprEvalStep_constval *scratch;
! 
! 					scratch = (ExprEvalStep_constval *)
! 						ExprEvalPushStep(state,
! 				                         EEOP_CONST,
! 										 resv,
! 										 resnull);
! 					scratch->value = Int32GetDatum(0);
! 					scratch->isnull = false;
  				}
  
  				/* Finally, examine the last comparison result */
! 				scratch = (ExprEvalStep_rowcompare_final *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_ROWCOMPARE_FINAL,
! 									 resv,
! 									 resnull);
! 				scratch->rctype = rcexpr->rctype;
  
  				/* adjust jump targetss */
  				foreach(lc, adjust_jumps)
  				{
! 					ExprEvalStep_rowcompare_step *as =
! 						(ExprEvalStep_rowcompare_step*)lfirst(lc);
  
  					Assert(as->opcode == EEOP_ROWCOMPARE_STEP);
! 					Assert(as->jumpdone == NULL);
! 					Assert(as->jumpnull == NULL);
  
  					/* jump to comparison evaluation */
! 					as->jumpdone = (ExprEvalStep *)scratch;
  					/* jump to the following expression */
! 					state->adjust_jumps = lappend(state->adjust_jumps,
! 					                              &as->jumpnull);
  				}
  
  				break;
***************
*** 1728,1745 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 */
  				foreach(lc, coalesce->args)
  				{
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					/* evaluate argument, directly into result datum */
  					ExecInitExprRec(e, parent, state, resv, resnull);
  
  					/* if it's not null, skip to end of COALESCE expr */
! 					scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
! 					scratch.d.jump.jumpdone = -1;	/* adjust later */
! 					ExprEvalPushStep(state, &scratch);
  
! 					adjust_jumps = lappend_int(adjust_jumps,
! 											   state->steps_len - 1);
  				}
  
  				/*
--- 1909,1929 ----
  				 */
  				foreach(lc, coalesce->args)
  				{
+ 					ExprEvalStep_jump *scratch;
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					/* evaluate argument, directly into result datum */
  					ExecInitExprRec(e, parent, state, resv, resnull);
  
  					/* if it's not null, skip to end of COALESCE expr */
! 					scratch = (ExprEvalStep_jump *)
! 						ExprEvalPushStep(state,
! 				                         EEOP_JUMP_IF_NOT_NULL,
! 										 resv,
! 										 resnull);
! 					scratch->jumpdone = NULL;		/* adjust later */
  
! 					adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  				}
  
  				/*
***************
*** 1748,1774 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * returned.
  				 */
  
! 				/* adjust jump targets */
! 				foreach(lc, adjust_jumps)
! 				{
! 					ExprEvalStep *as = &state->steps[lfirst_int(lc)];
! 
! 					Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL);
! 					Assert(as->d.jump.jumpdone == -1);
! 					as->d.jump.jumpdone = state->steps_len;
! 				}
  
  				break;
  			}
  
  		case T_MinMaxExpr:
  			{
  				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
  				int			nelems = list_length(minmaxexpr->args);
  				TypeCacheEntry *typentry;
  				FmgrInfo   *finfo;
  				FunctionCallInfo fcinfo;
  				ListCell   *lc;
  				int			off;
  
  				/* Look up the btree comparison function for the datatype */
--- 1932,1954 ----
  				 * returned.
  				 */
  
! 				/* Resolve the jumpdone pointers when the next operator is created */
! 				state->adjust_jumps = adjust_jumps;
  
  				break;
  			}
  
  		case T_MinMaxExpr:
  			{
+ 				ExprEvalStep_minmax *scratch;
  				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
  				int			nelems = list_length(minmaxexpr->args);
  				TypeCacheEntry *typentry;
  				FmgrInfo   *finfo;
  				FunctionCallInfo fcinfo;
  				ListCell   *lc;
+ 				Datum      *values;
+ 				bool       *nulls;
  				int			off;
  
  				/* Look up the btree comparison function for the datatype */
***************
*** 1795,1811 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				InitFunctionCallInfoData(*fcinfo, finfo, 2,
  										 minmaxexpr->inputcollid, NULL, NULL);
  
- 				scratch.opcode = EEOP_MINMAX;
  				/* allocate space to store arguments */
! 				scratch.d.minmax.values =
! 					(Datum *) palloc(sizeof(Datum) * nelems);
! 				scratch.d.minmax.nulls =
! 					(bool *) palloc(sizeof(bool) * nelems);
! 				scratch.d.minmax.nelems = nelems;
! 
! 				scratch.d.minmax.op = minmaxexpr->op;
! 				scratch.d.minmax.finfo = finfo;
! 				scratch.d.minmax.fcinfo_data = fcinfo;
  
  				/* evaluate expressions into minmax->values/nulls */
  				off = 0;
--- 1975,1983 ----
  				InitFunctionCallInfoData(*fcinfo, finfo, 2,
  										 minmaxexpr->inputcollid, NULL, NULL);
  
  				/* allocate space to store arguments */
! 				values = (Datum *) palloc(sizeof(Datum) * nelems);
! 				nulls = (bool *) palloc(sizeof(bool) * nelems);
  
  				/* evaluate expressions into minmax->values/nulls */
  				off = 0;
***************
*** 1814,1876 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					ExecInitExprRec(e, parent, state,
! 									&scratch.d.minmax.values[off],
! 									&scratch.d.minmax.nulls[off]);
  					off++;
  				}
  
  				/* and push the final comparison */
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_SQLValueFunction:
  			{
  				SQLValueFunction *svf = (SQLValueFunction *) node;
  
! 				scratch.opcode = EEOP_SQLVALUEFUNCTION;
! 				scratch.d.sqlvaluefunction.svf = svf;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_XmlExpr:
  			{
  				XmlExpr    *xexpr = (XmlExpr *) node;
  				int			nnamed = list_length(xexpr->named_args);
  				int			nargs = list_length(xexpr->args);
  				int			off;
  				ListCell   *arg;
! 
! 				scratch.opcode = EEOP_XMLEXPR;
! 				scratch.d.xmlexpr.xexpr = xexpr;
  
  				/* allocate space for storing all the arguments */
  				if (nnamed)
  				{
! 					scratch.d.xmlexpr.named_argvalue =
! 						(Datum *) palloc(sizeof(Datum) * nnamed);
! 					scratch.d.xmlexpr.named_argnull =
! 						(bool *) palloc(sizeof(bool) * nnamed);
! 				}
! 				else
! 				{
! 					scratch.d.xmlexpr.named_argvalue = NULL;
! 					scratch.d.xmlexpr.named_argnull = NULL;
  				}
  
  				if (nargs)
  				{
! 					scratch.d.xmlexpr.argvalue =
! 						(Datum *) palloc(sizeof(Datum) * nargs);
! 					scratch.d.xmlexpr.argnull =
! 						(bool *) palloc(sizeof(bool) * nargs);
! 				}
! 				else
! 				{
! 					scratch.d.xmlexpr.argvalue = NULL;
! 					scratch.d.xmlexpr.argnull = NULL;
  				}
  
  				/* prepare argument execution */
--- 1986,2049 ----
  					Expr	   *e = (Expr *) lfirst(lc);
  
  					ExecInitExprRec(e, parent, state,
! 									&values[off],
! 									&nulls[off]);
  					off++;
  				}
  
  				/* and push the final comparison */
! 				scratch = (ExprEvalStep_minmax *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_MINMAX,
! 									 resv,
! 									 resnull);
! 				scratch->values = values;
! 				scratch->nulls = nulls;
! 				scratch->nelems = nelems;
! 				scratch->op = minmaxexpr->op;
! 				scratch->finfo = finfo;
! 				scratch->fcinfo_data = fcinfo;
  				break;
  			}
  
  		case T_SQLValueFunction:
  			{
+ 				ExprEvalStep_sqlvaluefunction *scratch;
  				SQLValueFunction *svf = (SQLValueFunction *) node;
  
! 				scratch = (ExprEvalStep_sqlvaluefunction *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_SQLVALUEFUNCTION,
! 									 resv,
! 									 resnull);
! 				scratch->svf = svf;
  				break;
  			}
  
  		case T_XmlExpr:
  			{
+ 				ExprEvalStep_xmlexpr *scratch;
  				XmlExpr    *xexpr = (XmlExpr *) node;
  				int			nnamed = list_length(xexpr->named_args);
  				int			nargs = list_length(xexpr->args);
  				int			off;
  				ListCell   *arg;
! 				Datum      *named_argvalue = NULL;
! 				bool       *named_argnull = NULL;
! 				Datum      *argvalue = NULL;
! 				bool       *argnull = NULL;
  
  				/* allocate space for storing all the arguments */
  				if (nnamed)
  				{
! 					named_argvalue = (Datum *) palloc(sizeof(Datum) * nnamed);
! 					named_argnull = (bool *) palloc(sizeof(bool) * nnamed);
  				}
  
  				if (nargs)
  				{
! 					argvalue = (Datum *) palloc(sizeof(Datum) * nargs);
! 					argnull = (bool *) palloc(sizeof(bool) * nargs);
  				}
  
  				/* prepare argument execution */
***************
*** 1880,1887 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					Expr	   *e = (Expr *) lfirst(arg);
  
  					ExecInitExprRec(e, parent, state,
! 									&scratch.d.xmlexpr.named_argvalue[off],
! 									&scratch.d.xmlexpr.named_argnull[off]);
  					off++;
  				}
  
--- 2053,2060 ----
  					Expr	   *e = (Expr *) lfirst(arg);
  
  					ExecInitExprRec(e, parent, state,
! 									&named_argvalue[off],
! 									&named_argnull[off]);
  					off++;
  				}
  
***************
*** 1891,1938 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  					Expr	   *e = (Expr *) lfirst(arg);
  
  					ExecInitExprRec(e, parent, state,
! 									&scratch.d.xmlexpr.argvalue[off],
! 									&scratch.d.xmlexpr.argnull[off]);
  					off++;
  				}
  
  				/* and evaluate the actual XML expression */
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_NullTest:
  			{
  				NullTest   *ntest = (NullTest *) node;
  
  				if (ntest->nulltesttype == IS_NULL)
  				{
  					if (ntest->argisrow)
! 						scratch.opcode = EEOP_NULLTEST_ROWISNULL;
  					else
! 						scratch.opcode = EEOP_NULLTEST_ISNULL;
  				}
  				else if (ntest->nulltesttype == IS_NOT_NULL)
  				{
  					if (ntest->argisrow)
! 						scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL;
  					else
! 						scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
  				}
  				else
  				{
  					elog(ERROR, "unrecognized nulltesttype: %d",
  						 (int) ntest->nulltesttype);
  				}
- 				/* initialize cache in case it's a row test */
- 				scratch.d.nulltest_row.argdesc = NULL;
  
  				/* first evaluate argument into result variable */
  				ExecInitExprRec(ntest->arg, parent, state,
  								resv, resnull);
  
  				/* then push the test of that argument */
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 2064,2127 ----
  					Expr	   *e = (Expr *) lfirst(arg);
  
  					ExecInitExprRec(e, parent, state,
! 									&argvalue[off],
! 									&argnull[off]);
  					off++;
  				}
  
  				/* and evaluate the actual XML expression */
! 				scratch = (ExprEvalStep_xmlexpr *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_XMLEXPR,
! 									 resv,
! 									 resnull);
! 				scratch->xexpr = xexpr;
! 				scratch->named_argvalue = named_argvalue;
! 				scratch->named_argnull = named_argnull;
! 				scratch->argvalue = argvalue;
! 				scratch->argnull = argnull;
  				break;
  			}
  
  		case T_NullTest:
  			{
+ 				ExprEvalStep_nulltest_row *scratch;
  				NullTest   *ntest = (NullTest *) node;
  
  				if (ntest->nulltesttype == IS_NULL)
  				{
  					if (ntest->argisrow)
! 						opcode = EEOP_NULLTEST_ROWISNULL;
  					else
! 						opcode = EEOP_NULLTEST_ISNULL;
  				}
  				else if (ntest->nulltesttype == IS_NOT_NULL)
  				{
  					if (ntest->argisrow)
! 						opcode = EEOP_NULLTEST_ROWISNOTNULL;
  					else
! 						opcode = EEOP_NULLTEST_ISNOTNULL;
  				}
  				else
  				{
  					elog(ERROR, "unrecognized nulltesttype: %d",
  						 (int) ntest->nulltesttype);
  				}
  
  				/* first evaluate argument into result variable */
  				ExecInitExprRec(ntest->arg, parent, state,
  								resv, resnull);
  
  				/* then push the test of that argument */
! 				scratch = (ExprEvalStep_nulltest_row *)
! 					ExprEvalPushStep(state,
! 			                         opcode,
! 									 resv,
! 									 resnull);
! 
! 				/* initialize cache in case it's a row test */
! 				scratch->argdesc = NULL;
! 
  				break;
  			}
  
***************
*** 1951,1981 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				switch (btest->booltesttype)
  				{
  					case IS_TRUE:
! 						scratch.opcode = EEOP_BOOLTEST_IS_TRUE;
  						break;
  					case IS_NOT_TRUE:
! 						scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE;
  						break;
  					case IS_FALSE:
! 						scratch.opcode = EEOP_BOOLTEST_IS_FALSE;
  						break;
  					case IS_NOT_FALSE:
! 						scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE;
  						break;
  					case IS_UNKNOWN:
  						/* Same as scalar IS NULL test */
! 						scratch.opcode = EEOP_NULLTEST_ISNULL;
  						break;
  					case IS_NOT_UNKNOWN:
  						/* Same as scalar IS NOT NULL test */
! 						scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
  						break;
  					default:
  						elog(ERROR, "unrecognized booltesttype: %d",
  							 (int) btest->booltesttype);
  				}
  
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 2140,2173 ----
  				switch (btest->booltesttype)
  				{
  					case IS_TRUE:
! 						opcode = EEOP_BOOLTEST_IS_TRUE;
  						break;
  					case IS_NOT_TRUE:
! 						opcode = EEOP_BOOLTEST_IS_NOT_TRUE;
  						break;
  					case IS_FALSE:
! 						opcode = EEOP_BOOLTEST_IS_FALSE;
  						break;
  					case IS_NOT_FALSE:
! 						opcode = EEOP_BOOLTEST_IS_NOT_FALSE;
  						break;
  					case IS_UNKNOWN:
  						/* Same as scalar IS NULL test */
! 						opcode = EEOP_NULLTEST_ISNULL;
  						break;
  					case IS_NOT_UNKNOWN:
  						/* Same as scalar IS NOT NULL test */
! 						opcode = EEOP_NULLTEST_ISNOTNULL;
  						break;
  					default:
  						elog(ERROR, "unrecognized booltesttype: %d",
  							 (int) btest->booltesttype);
  				}
  
! 				ExprEvalPushStep(state,
! 		                         opcode,
! 								 resv,
! 								 resnull);
  				break;
  			}
  
***************
*** 1983,1995 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  			{
  				CoerceToDomain *ctest = (CoerceToDomain *) node;
  
! 				ExecInitCoerceToDomain(&scratch, ctest, parent, state,
  									   resv, resnull);
  				break;
  			}
  
  		case T_CoerceToDomainValue:
  			{
  				/*
  				 * Read from location identified by innermost_domainval.  Note
  				 * that innermost_domainval could be NULL, if we're compiling
--- 2175,2189 ----
  			{
  				CoerceToDomain *ctest = (CoerceToDomain *) node;
  
! 				ExecInitCoerceToDomain(ctest, parent, state,
  									   resv, resnull);
  				break;
  			}
  
  		case T_CoerceToDomainValue:
  			{
+ 				ExprEvalStep_casetest *scratch;
+ 
  				/*
  				 * Read from location identified by innermost_domainval.  Note
  				 * that innermost_domainval could be NULL, if we're compiling
***************
*** 1998,2028 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  				 * econtext->domainValue_datum.  We'll take care of that
  				 * scenario at runtime.
  				 */
! 				scratch.opcode = EEOP_DOMAIN_TESTVAL;
  				/* we share instruction union variant with case testval */
! 				scratch.d.casetest.value = state->innermost_domainval;
! 				scratch.d.casetest.isnull = state->innermost_domainnull;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_CurrentOfExpr:
  			{
! 				scratch.opcode = EEOP_CURRENTOFEXPR;
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
  		case T_NextValueExpr:
  			{
  				NextValueExpr *nve = (NextValueExpr *) node;
  
! 				scratch.opcode = EEOP_NEXTVALUEEXPR;
! 				scratch.d.nextvalueexpr.seqid = nve->seqid;
! 				scratch.d.nextvalueexpr.seqtypid = nve->typeId;
! 
! 				ExprEvalPushStep(state, &scratch);
  				break;
  			}
  
--- 2192,2230 ----
  				 * econtext->domainValue_datum.  We'll take care of that
  				 * scenario at runtime.
  				 */
! 				scratch = (ExprEvalStep_casetest *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_DOMAIN_TESTVAL,
! 									 resv,
! 									 resnull);
! 
  				/* we share instruction union variant with case testval */
! 				scratch->value = state->innermost_domainval;
! 				scratch->isnull = state->innermost_domainnull;
  				break;
  			}
  
  		case T_CurrentOfExpr:
  			{
! 				ExprEvalPushStep(state,
! 		                         EEOP_CURRENTOFEXPR,
! 								 resv,
! 								 resnull);
  				break;
  			}
  
  		case T_NextValueExpr:
  			{
+ 				ExprEvalStep_nextvalueexpr *scratch;
  				NextValueExpr *nve = (NextValueExpr *) node;
  
! 				scratch = (ExprEvalStep_nextvalueexpr *)
! 					ExprEvalPushStep(state,
! 			                         EEOP_NEXTVALUEEXPR,
! 									 resv,
! 									 resnull);
! 				scratch->seqid = nve->seqid;
! 				scratch->seqtypid = nve->typeId;
  				break;
  			}
  
***************
*** 2035,2074 **** ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
  
  /*
   * Add another expression evaluation step to ExprState->steps.
-  *
-  * Note that this potentially re-allocates es->steps, therefore no pointer
-  * into that array may be used while the expression is still being built.
   */
! static void
! ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
  {
! 	if (es->steps_alloc == 0)
  	{
! 		es->steps_alloc = 16;
! 		es->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc);
  	}
! 	else if (es->steps_alloc == es->steps_len)
  	{
! 		es->steps_alloc *= 2;
! 		es->steps = repalloc(es->steps,
! 							 sizeof(ExprEvalStep) * es->steps_alloc);
  	}
  
! 	memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep));
  }
  
  /*
   * Perform setup necessary for the evaluation of a function-like expression,
   * appending argument evaluation steps to the steps list in *state, and
!  * setting up *scratch so it is ready to be pushed.
   *
!  * *scratch is not pushed here, so that callers may override the opcode,
!  * which is useful for function-like cases like DISTINCT.
   */
  static void
! ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
! 			 Oid inputcollid, PlanState *parent, ExprState *state)
  {
  	int			nargs = list_length(args);
  	AclResult	aclresult;
  	FmgrInfo   *flinfo;
--- 2237,2309 ----
  
  /*
   * Add another expression evaluation step to ExprState->steps.
   */
! static ExprEvalStep *
! ExprEvalPushStep(ExprState *es, const ExprEvalOp opcode,
!                  Datum *resv, bool *resnull)
  {
! 	size_t size = eeop_size[opcode];
! 	ExprEvalStep *result = NULL;
! 
! 	if (es->buffer_size < size)
  	{
! 		es->buffer_size = MAX(128, size);
! 		es->step_buffer = palloc0(es->buffer_size);
! 
! 		if (es->steps == NULL)
! 			es->steps = (ExprEvalStep *)es->step_buffer;
  	}
! 
! 	result = (ExprEvalStep *)es->step_buffer;
! 	result->opcode = opcode;
! 	result->resvalue = resv;
! 	result->resnull = resnull;
! 
! 	/* Chain steps */
! 	if (es->last_step != NULL)
! 		es->last_step->nextstep = result;
! 	es->last_step = result;
! 
! 	/* Adjust jump targets if needed */
! 	if (es->adjust_jumps != NIL)
  	{
! 		ListCell   *lc;
! 
! 		foreach(lc, es->adjust_jumps)
! 		{
! 			ExprEvalStep **as = (ExprEvalStep **)lfirst(lc);
! 
! 			Assert(*as == NULL);
! 			*as = result;
! 		}
! 
! 		/* We're done with these pointers */
! 		es->adjust_jumps = NIL;
  	}
  
! 	/* Advance buffer */
! 	es->step_buffer += size;
! 	es->buffer_size -= size;
! 	++es->num_steps;
! 
! 	return result;
  }
  
  /*
   * Perform setup necessary for the evaluation of a function-like expression,
   * appending argument evaluation steps to the steps list in *state, and
!  * pushing the operator.
   *
!  * Callers may override the opcode, which is useful for function-like cases
!  * like DISTINCT, by adjusting state->last_step.
   */
  static void
! ExecInitFunc(Expr *node, List *args, Oid funcid,
! 			 Oid inputcollid, PlanState *parent, ExprState *state,
! 			 Datum *resv, bool *resnull)
  {
+ 	ExprEvalStep_func *scratch;
+ 	ExprEvalOp  opcode;
  	int			nargs = list_length(args);
  	AclResult	aclresult;
  	FmgrInfo   *flinfo;
***************
*** 2097,2106 **** ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
  							   FUNC_MAX_ARGS)));
  
  	/* Allocate function lookup data and parameter workspace for this call */
! 	scratch->d.func.finfo = palloc0(sizeof(FmgrInfo));
! 	scratch->d.func.fcinfo_data = palloc0(sizeof(FunctionCallInfoData));
! 	flinfo = scratch->d.func.finfo;
! 	fcinfo = scratch->d.func.fcinfo_data;
  
  	/* Set up the primary fmgr lookup information */
  	fmgr_info(funcid, flinfo);
--- 2332,2339 ----
  							   FUNC_MAX_ARGS)));
  
  	/* Allocate function lookup data and parameter workspace for this call */
! 	flinfo = palloc0(sizeof(FmgrInfo));
! 	fcinfo = palloc0(sizeof(FunctionCallInfoData));
  
  	/* Set up the primary fmgr lookup information */
  	fmgr_info(funcid, flinfo);
***************
*** 2110,2119 **** ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
  	InitFunctionCallInfoData(*fcinfo, flinfo,
  							 nargs, inputcollid, NULL, NULL);
  
- 	/* Keep extra copies of this info to save an indirection at runtime */
- 	scratch->d.func.fn_addr = flinfo->fn_addr;
- 	scratch->d.func.nargs = nargs;
- 
  	/* We only support non-set functions here */
  	if (flinfo->fn_retset)
  		ereport(ERROR,
--- 2343,2348 ----
***************
*** 2151,2167 **** ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
  	if (pgstat_track_functions <= flinfo->fn_stats)
  	{
  		if (flinfo->fn_strict && nargs > 0)
! 			scratch->opcode = EEOP_FUNCEXPR_STRICT;
  		else
! 			scratch->opcode = EEOP_FUNCEXPR;
  	}
  	else
  	{
  		if (flinfo->fn_strict && nargs > 0)
! 			scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE;
  		else
! 			scratch->opcode = EEOP_FUNCEXPR_FUSAGE;
  	}
  }
  
  /*
--- 2380,2407 ----
  	if (pgstat_track_functions <= flinfo->fn_stats)
  	{
  		if (flinfo->fn_strict && nargs > 0)
! 			opcode = EEOP_FUNCEXPR_STRICT;
  		else
! 			opcode = EEOP_FUNCEXPR;
  	}
  	else
  	{
  		if (flinfo->fn_strict && nargs > 0)
! 			opcode = EEOP_FUNCEXPR_STRICT_FUSAGE;
  		else
! 			opcode = EEOP_FUNCEXPR_FUSAGE;
  	}
+ 	scratch = (ExprEvalStep_func *)
+ 		ExprEvalPushStep(state,
+                          opcode,
+ 						 resv,
+ 						 resnull);
+ 	scratch->finfo = flinfo;
+ 	scratch->fcinfo_data = fcinfo;
+ 
+ 	/* Keep extra copies of function info to save an indirection at runtime */
+ 	scratch->fn_addr = flinfo->fn_addr;
+ 	scratch->nargs = nargs;
  }
  
  /*
***************
*** 2172,2178 **** static void
  ExecInitExprSlots(ExprState *state, Node *node)
  {
  	LastAttnumInfo info = {0, 0, 0};
! 	ExprEvalStep scratch;
  
  	/*
  	 * Figure out which attributes we're going to need.
--- 2412,2418 ----
  ExecInitExprSlots(ExprState *state, Node *node)
  {
  	LastAttnumInfo info = {0, 0, 0};
! 	ExprEvalStep_fetch *scratch;
  
  	/*
  	 * Figure out which attributes we're going to need.
***************
*** 2182,2202 **** ExecInitExprSlots(ExprState *state, Node *node)
  	/* Emit steps as needed */
  	if (info.last_inner > 0)
  	{
! 		scratch.opcode = EEOP_INNER_FETCHSOME;
! 		scratch.d.fetch.last_var = info.last_inner;
! 		ExprEvalPushStep(state, &scratch);
  	}
  	if (info.last_outer > 0)
  	{
! 		scratch.opcode = EEOP_OUTER_FETCHSOME;
! 		scratch.d.fetch.last_var = info.last_outer;
! 		ExprEvalPushStep(state, &scratch);
  	}
  	if (info.last_scan > 0)
  	{
! 		scratch.opcode = EEOP_SCAN_FETCHSOME;
! 		scratch.d.fetch.last_var = info.last_scan;
! 		ExprEvalPushStep(state, &scratch);
  	}
  }
  
--- 2422,2451 ----
  	/* Emit steps as needed */
  	if (info.last_inner > 0)
  	{
! 		scratch = (ExprEvalStep_fetch *)
! 			ExprEvalPushStep(state,
!    	                         EEOP_INNER_FETCHSOME,
! 							 NULL,
! 							 NULL);
! 		scratch->last_var = info.last_inner;
  	}
  	if (info.last_outer > 0)
  	{
! 		scratch = (ExprEvalStep_fetch *)
! 			ExprEvalPushStep(state,
!    	                         EEOP_OUTER_FETCHSOME,
! 							 NULL,
! 							 NULL);
! 		scratch->last_var = info.last_outer;
  	}
  	if (info.last_scan > 0)
  	{
! 		scratch = (ExprEvalStep_fetch *)
! 			ExprEvalPushStep(state,
!    	                         EEOP_SCAN_FETCHSOME,
! 							 NULL,
! 							 NULL);
! 		scratch->last_var = info.last_scan;
  	}
  }
  
***************
*** 2249,2267 **** get_last_attnums_walker(Node *node, LastAttnumInfo *info)
  }
  
  /*
!  * Prepare step for the evaluation of a whole-row variable.
!  * The caller still has to push the step.
   */
  static void
! ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
  {
  	/* fill in all but the target */
! 	scratch->opcode = EEOP_WHOLEROW;
! 	scratch->d.wholerow.var = variable;
! 	scratch->d.wholerow.first = true;
! 	scratch->d.wholerow.slow = false;
! 	scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */
! 	scratch->d.wholerow.junkFilter = NULL;
  
  	/*
  	 * If the input tuple came from a subquery, it might contain "resjunk"
--- 2498,2522 ----
  }
  
  /*
!  * Prepare and push step for the evaluation of a whole-row variable.
   */
  static void
! ExecInitWholeRowVar(ExprState *state, Var *variable,
!                     PlanState *parent, Datum *resv, bool *resnull)
  {
+ 	ExprEvalStep_wholerow *scratch;
+ 
  	/* fill in all but the target */
! 	scratch = (ExprEvalStep_wholerow *)
! 		ExprEvalPushStep(state,
!                          EEOP_WHOLEROW,
! 						 resv,
! 						 resnull);
! 	scratch->var = variable;
! 	scratch->first = true;
! 	scratch->slow = false;
! 	scratch->tupdesc = NULL; /* filled at runtime */
! 	scratch->junkFilter = NULL;
  
  	/*
  	 * If the input tuple came from a subquery, it might contain "resjunk"
***************
*** 2311,2317 **** ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
  			/* If so, build the junkfilter now */
  			if (junk_filter_needed)
  			{
! 				scratch->d.wholerow.junkFilter =
  					ExecInitJunkFilter(subplan->plan->targetlist,
  									   ExecGetResultType(subplan)->tdhasoid,
  									   ExecInitExtraTupleSlot(parent->state));
--- 2566,2572 ----
  			/* If so, build the junkfilter now */
  			if (junk_filter_needed)
  			{
! 				scratch->junkFilter =
  					ExecInitJunkFilter(subplan->plan->targetlist,
  									   ExecGetResultType(subplan)->tdhasoid,
  									   ExecInitExtraTupleSlot(parent->state));
***************
*** 2324,2330 **** ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
   * Prepare evaluation of an ArrayRef expression.
   */
  static void
! ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  				 ExprState *state, Datum *resv, bool *resnull)
  {
  	bool		isAssignment = (aref->refassgnexpr != NULL);
--- 2579,2585 ----
   * Prepare evaluation of an ArrayRef expression.
   */
  static void
! ExecInitArrayRef(ArrayRef *aref, PlanState *parent,
  				 ExprState *state, Datum *resv, bool *resnull)
  {
  	bool		isAssignment = (aref->refassgnexpr != NULL);
***************
*** 2357,2367 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  	 */
  	if (!isAssignment)
  	{
! 		scratch->opcode = EEOP_JUMP_IF_NULL;
! 		scratch->d.jump.jumpdone = -1;	/* adjust later */
! 		ExprEvalPushStep(state, scratch);
! 		adjust_jumps = lappend_int(adjust_jumps,
! 								   state->steps_len - 1);
  	}
  
  	/* Verify subscript list lengths are within limit */
--- 2612,2626 ----
  	 */
  	if (!isAssignment)
  	{
! 		ExprEvalStep_jump *scratch;
! 
! 		scratch = (ExprEvalStep_jump *)
! 			ExprEvalPushStep(state,
!        	                     EEOP_JUMP_IF_NULL,
! 							 resv,
! 							 resnull);
! 		scratch->jumpdone = NULL;	/* adjust later */
! 		adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  	}
  
  	/* Verify subscript list lengths are within limit */
***************
*** 2381,2386 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
--- 2640,2646 ----
  	i = 0;
  	foreach(lc, aref->refupperindexpr)
  	{
+ 		ExprEvalStep_arrayref_subscript *scratch;
  		Expr	   *e = (Expr *) lfirst(lc);
  
  		/* When slicing, individual subscript bounds can be omitted */
***************
*** 2398,2411 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
! 		scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT;
! 		scratch->d.arrayref_subscript.state = arefstate;
! 		scratch->d.arrayref_subscript.off = i;
! 		scratch->d.arrayref_subscript.isupper = true;
! 		scratch->d.arrayref_subscript.jumpdone = -1;	/* adjust later */
! 		ExprEvalPushStep(state, scratch);
! 		adjust_jumps = lappend_int(adjust_jumps,
! 								   state->steps_len - 1);
  		i++;
  	}
  	arefstate->numupper = i;
--- 2658,2673 ----
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
! 		scratch = (ExprEvalStep_arrayref_subscript *)
! 			ExprEvalPushStep(state,
!        	                     EEOP_ARRAYREF_SUBSCRIPT,
! 							 resv,
! 							 resnull);
! 		scratch->state = arefstate;
! 		scratch->off = i;
! 		scratch->isupper = true;
! 		scratch->jumpdone = NULL;	/* adjust later */
! 		adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  		i++;
  	}
  	arefstate->numupper = i;
***************
*** 2414,2419 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
--- 2676,2682 ----
  	i = 0;
  	foreach(lc, aref->reflowerindexpr)
  	{
+ 		ExprEvalStep_arrayref_subscript *scratch;
  		Expr	   *e = (Expr *) lfirst(lc);
  
  		/* When slicing, individual subscript bounds can be omitted */
***************
*** 2431,2444 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
! 		scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT;
! 		scratch->d.arrayref_subscript.state = arefstate;
! 		scratch->d.arrayref_subscript.off = i;
! 		scratch->d.arrayref_subscript.isupper = false;
! 		scratch->d.arrayref_subscript.jumpdone = -1;	/* adjust later */
! 		ExprEvalPushStep(state, scratch);
! 		adjust_jumps = lappend_int(adjust_jumps,
! 								   state->steps_len - 1);
  		i++;
  	}
  	arefstate->numlower = i;
--- 2694,2709 ----
  						&arefstate->subscriptvalue, &arefstate->subscriptnull);
  
  		/* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
! 		scratch = (ExprEvalStep_arrayref_subscript *)
! 			ExprEvalPushStep(state,
!        	                     EEOP_ARRAYREF_SUBSCRIPT,
! 							 resv,
! 							 resnull);
! 		scratch->state = arefstate;
! 		scratch->off = i;
! 		scratch->isupper = false;
! 		scratch->jumpdone = NULL;	/* adjust later */
! 		adjust_jumps = lappend(adjust_jumps, &scratch->jumpdone);
  		i++;
  	}
  	arefstate->numlower = i;
***************
*** 2450,2455 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
--- 2715,2721 ----
  
  	if (isAssignment)
  	{
+ 		ExprEvalStep_arrayref *scratch;
  		Datum	   *save_innermost_caseval;
  		bool	   *save_innermost_casenull;
  
***************
*** 2469,2477 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  		 */
  		if (isAssignmentIndirectionExpr(aref->refassgnexpr))
  		{
! 			scratch->opcode = EEOP_ARRAYREF_OLD;
! 			scratch->d.arrayref.state = arefstate;
! 			ExprEvalPushStep(state, scratch);
  		}
  
  		/* ARRAYREF_OLD puts extracted value into prevvalue/prevnull */
--- 2735,2746 ----
  		 */
  		if (isAssignmentIndirectionExpr(aref->refassgnexpr))
  		{
! 			scratch = (ExprEvalStep_arrayref *)
! 				ExprEvalPushStep(state,
!    	    	                     EEOP_ARRAYREF_OLD,
! 								 resv,
! 								 resnull);
! 			scratch->state = arefstate;
  		}
  
  		/* ARRAYREF_OLD puts extracted value into prevvalue/prevnull */
***************
*** 2488,2522 **** ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
  		state->innermost_casenull = save_innermost_casenull;
  
  		/* and perform the assignment */
! 		scratch->opcode = EEOP_ARRAYREF_ASSIGN;
! 		scratch->d.arrayref.state = arefstate;
! 		ExprEvalPushStep(state, scratch);
  	}
  	else
  	{
  		/* array fetch is much simpler */
! 		scratch->opcode = EEOP_ARRAYREF_FETCH;
! 		scratch->d.arrayref.state = arefstate;
! 		ExprEvalPushStep(state, scratch);
  	}
  
! 	/* adjust jump targets */
! 	foreach(lc, adjust_jumps)
! 	{
! 		ExprEvalStep *as = &state->steps[lfirst_int(lc)];
! 
! 		if (as->opcode == EEOP_ARRAYREF_SUBSCRIPT)
! 		{
! 			Assert(as->d.arrayref_subscript.jumpdone == -1);
! 			as->d.arrayref_subscript.jumpdone = state->steps_len;
! 		}
! 		else
! 		{
! 			Assert(as->opcode == EEOP_JUMP_IF_NULL);
! 			Assert(as->d.jump.jumpdone == -1);
! 			as->d.jump.jumpdone = state->steps_len;
! 		}
! 	}
  }
  
  /*
--- 2757,2784 ----
  		state->innermost_casenull = save_innermost_casenull;
  
  		/* and perform the assignment */
! 		scratch = (ExprEvalStep_arrayref *)
! 			ExprEvalPushStep(state,
!        	                     EEOP_ARRAYREF_ASSIGN,
! 							 resv,
! 							 resnull);
! 		scratch->state = arefstate;
  	}
  	else
  	{
+ 		ExprEvalStep_arrayref *scratch;
+ 
  		/* array fetch is much simpler */
! 		scratch = (ExprEvalStep_arrayref *)
! 			ExprEvalPushStep(state,
!        	                     EEOP_ARRAYREF_FETCH,
! 							 resv,
! 							 resnull);
! 		scratch->state = arefstate;
  	}
  
! 	/* Resolve the jumpdone pointers when the next operator is created */
! 	state->adjust_jumps = adjust_jumps;
  }
  
  /*
***************
*** 2558,2568 **** isAssignmentIndirectionExpr(Expr *expr)
   * Prepare evaluation of a CoerceToDomain expression.
   */
  static void
! ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
  					   PlanState *parent, ExprState *state,
  					   Datum *resv, bool *resnull)
  {
! 	ExprEvalStep scratch2;
  	DomainConstraintRef *constraint_ref;
  	Datum	   *domainval = NULL;
  	bool	   *domainnull = NULL;
--- 2820,2830 ----
   * Prepare evaluation of a CoerceToDomain expression.
   */
  static void
! ExecInitCoerceToDomain(CoerceToDomain *ctest,
  					   PlanState *parent, ExprState *state,
  					   Datum *resv, bool *resnull)
  {
! 	ExprEvalStep_domaincheck *scratch;
  	DomainConstraintRef *constraint_ref;
  	Datum	   *domainval = NULL;
  	bool	   *domainnull = NULL;
***************
*** 2570,2579 **** ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
  	bool	   *save_innermost_domainnull;
  	ListCell   *l;
  
- 	scratch->d.domaincheck.resulttype = ctest->resulttype;
  	/* we'll allocate workspace only if needed */
! 	scratch->d.domaincheck.checkvalue = NULL;
! 	scratch->d.domaincheck.checknull = NULL;
  
  	/*
  	 * Evaluate argument - it's fine to directly store it into resv/resnull,
--- 2832,2840 ----
  	bool	   *save_innermost_domainnull;
  	ListCell   *l;
  
  	/* we'll allocate workspace only if needed */
! 	Datum      *checkvalue = NULL;
! 	bool       *checknull = NULL;
  
  	/*
  	 * Evaluate argument - it's fine to directly store it into resv/resnull,
***************
*** 2615,2636 **** ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
  	{
  		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
  
- 		scratch->d.domaincheck.constraintname = con->name;
- 
  		switch (con->constrainttype)
  		{
  			case DOM_CONSTRAINT_NOTNULL:
! 				scratch->opcode = EEOP_DOMAIN_NOTNULL;
! 				ExprEvalPushStep(state, scratch);
  				break;
  			case DOM_CONSTRAINT_CHECK:
  				/* Allocate workspace for CHECK output if we didn't yet */
! 				if (scratch->d.domaincheck.checkvalue == NULL)
  				{
! 					scratch->d.domaincheck.checkvalue =
! 						(Datum *) palloc(sizeof(Datum));
! 					scratch->d.domaincheck.checknull =
! 						(bool *) palloc(sizeof(bool));
  				}
  
  				/*
--- 2876,2901 ----
  	{
  		DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
  
  		switch (con->constrainttype)
  		{
  			case DOM_CONSTRAINT_NOTNULL:
! 				scratch = (ExprEvalStep_domaincheck *)
! 					ExprEvalPushStep(state,
! 		       	                     EEOP_DOMAIN_NOTNULL,
! 									 resv,
! 									 resnull);
! 				scratch->resulttype = ctest->resulttype;
! 				scratch->checkvalue = NULL;
! 				scratch->checknull = NULL;
! 				scratch->constraintname = con->name;
! 
  				break;
  			case DOM_CONSTRAINT_CHECK:
  				/* Allocate workspace for CHECK output if we didn't yet */
! 				if (checkvalue == NULL)
  				{
! 					checkvalue = (Datum *) palloc(sizeof(Datum));
! 					checknull = (bool *) palloc(sizeof(bool));
  				}
  
  				/*
***************
*** 2645,2661 **** ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
  					 */
  					if (get_typlen(ctest->resulttype) == -1)
  					{
  						/* Yes, so make output workspace for MAKE_READONLY */
  						domainval = (Datum *) palloc(sizeof(Datum));
  						domainnull = (bool *) palloc(sizeof(bool));
  
  						/* Emit MAKE_READONLY */
! 						scratch2.opcode = EEOP_MAKE_READONLY;
! 						scratch2.resvalue = domainval;
! 						scratch2.resnull = domainnull;
! 						scratch2.d.make_readonly.value = resv;
! 						scratch2.d.make_readonly.isnull = resnull;
! 						ExprEvalPushStep(state, &scratch2);
  					}
  					else
  					{
--- 2910,2929 ----
  					 */
  					if (get_typlen(ctest->resulttype) == -1)
  					{
+ 						ExprEvalStep_make_readonly *scratch2;
+ 
  						/* Yes, so make output workspace for MAKE_READONLY */
  						domainval = (Datum *) palloc(sizeof(Datum));
  						domainnull = (bool *) palloc(sizeof(bool));
  
  						/* Emit MAKE_READONLY */
! 						scratch2 = (ExprEvalStep_make_readonly *)
! 							ExprEvalPushStep(state,
! 				       	                     EEOP_MAKE_READONLY,
! 											 domainval,
! 											 domainnull);
! 						scratch2->value = resv;
! 						scratch2->isnull = resnull;
  					}
  					else
  					{
***************
*** 2678,2692 **** ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
  
  				/* evaluate check expression value */
  				ExecInitExprRec(con->check_expr, parent, state,
! 								scratch->d.domaincheck.checkvalue,
! 								scratch->d.domaincheck.checknull);
  
  				state->innermost_domainval = save_innermost_domainval;
  				state->innermost_domainnull = save_innermost_domainnull;
  
  				/* now test result */
! 				scratch->opcode = EEOP_DOMAIN_CHECK;
! 				ExprEvalPushStep(state, scratch);
  
  				break;
  			default:
--- 2946,2966 ----
  
  				/* evaluate check expression value */
  				ExecInitExprRec(con->check_expr, parent, state,
! 								checkvalue, checknull);
  
  				state->innermost_domainval = save_innermost_domainval;
  				state->innermost_domainnull = save_innermost_domainnull;
  
  				/* now test result */
! 				scratch = (ExprEvalStep_domaincheck *)
! 					ExprEvalPushStep(state,
! 		       	                     EEOP_DOMAIN_CHECK,
! 									 resv,
! 									 resnull);
! 				scratch->resulttype = ctest->resulttype;
! 				scratch->checkvalue = checkvalue;
! 				scratch->checknull = checknull;
! 				scratch->constraintname = con->name;
  
  				break;
  			default:
*** a/src/backend/executor/execExprInterp.c
--- b/src/backend/executor/execExprInterp.c
***************
*** 116,128 **** static const void **dispatch_table = NULL;
  
  #define EEO_NEXT() \
  	do { \
! 		op++; \
  		EEO_DISPATCH(); \
  	} while (0)
  
! #define EEO_JUMP(stepno) \
  	do { \
! 		op = &state->steps[stepno]; \
  		EEO_DISPATCH(); \
  	} while (0)
  
--- 116,128 ----
  
  #define EEO_NEXT() \
  	do { \
! 		op = op->nextstep; \
  		EEO_DISPATCH(); \
  	} while (0)
  
! #define EEO_JUMP(step) \
  	do { \
! 		op = step; \
  		EEO_DISPATCH(); \
  	} while (0)
  
***************
*** 135,141 **** static void CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vart
  static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
  				   TupleDesc *cache_field, ExprContext *econtext);
  static void ShutdownTupleDescRef(Datum arg);
! static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
  				   ExprContext *econtext, bool checkisnull);
  
  /* fast-path evaluation functions */
--- 135,141 ----
  static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
  				   TupleDesc *cache_field, ExprContext *econtext);
  static void ShutdownTupleDescRef(Datum arg);
! static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep_nulltest_row *sop,
  				   ExprContext *econtext, bool checkisnull);
  
  /* fast-path evaluation functions */
***************
*** 152,157 **** static Datum ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool
--- 152,163 ----
  
  
  /*
+  * Macro to define a specific operator "sop" by casting the generic "op"
+  * to the right type.
+  */
+ #define DEF_SOP(type) type *sop = (type *)op
+ 
+ /*
   * Prepare ExprState for interpreted execution.
   */
  void
***************
*** 161,168 **** ExecReadyInterpretedExpr(ExprState *state)
  	ExecInitInterpreter();
  
  	/* Simple validity checks on expression */
! 	Assert(state->steps_len >= 1);
! 	Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE);
  
  	/*
  	 * Don't perform redundant initialization. This is unreachable in current
--- 167,173 ----
  	ExecInitInterpreter();
  
  	/* Simple validity checks on expression */
! 	Assert(state->steps != NULL);
  
  	/*
  	 * Don't perform redundant initialization. This is unreachable in current
***************
*** 189,198 **** ExecReadyInterpretedExpr(ExprState *state)
  	 * enough that the overhead of ExecInterpExpr matters.  For more complex
  	 * expressions it's cheaper to use ExecInterpExpr always.
  	 */
! 	if (state->steps_len == 3)
  	{
! 		ExprEvalOp	step0 = state->steps[0].opcode;
! 		ExprEvalOp	step1 = state->steps[1].opcode;
  
  		if (step0 == EEOP_INNER_FETCHSOME &&
  			step1 == EEOP_INNER_VAR_FIRST)
--- 194,203 ----
  	 * enough that the overhead of ExecInterpExpr matters.  For more complex
  	 * expressions it's cheaper to use ExecInterpExpr always.
  	 */
! 	if (state->num_steps == 3)
  	{
! 		ExprEvalOp	step0 = state->steps->opcode;
! 		ExprEvalOp	step1 = state->steps->nextstep->opcode;
  
  		if (step0 == EEOP_INNER_FETCHSOME &&
  			step1 == EEOP_INNER_VAR_FIRST)
***************
*** 231,238 **** ExecReadyInterpretedExpr(ExprState *state)
  			return;
  		}
  	}
! 	else if (state->steps_len == 2 &&
! 			 state->steps[0].opcode == EEOP_CONST)
  	{
  		state->evalfunc = ExecJustConst;
  		return;
--- 236,243 ----
  			return;
  		}
  	}
! 	else if (state->num_steps == 2 &&
! 			 state->steps->opcode == EEOP_CONST)
  	{
  		state->evalfunc = ExecJustConst;
  		return;
***************
*** 245,258 **** ExecReadyInterpretedExpr(ExprState *state)
  	 * address to jump to.  (Use ExecEvalStepOp() to get back the opcode.)
  	 */
  	{
! 		int			off;
! 
! 		for (off = 0; off < state->steps_len; off++)
! 		{
! 			ExprEvalStep *op = &state->steps[off];
  
  			op->opcode = EEO_OPCODE(op->opcode);
- 		}
  
  		state->flags |= EEO_FLAG_DIRECT_THREADED;
  	}
--- 250,264 ----
  	 * address to jump to.  (Use ExecEvalStepOp() to get back the opcode.)
  	 */
  	{
! 		ExprEvalStep *op = state->steps;
  
+ 		/*
+ 		 * We explicitly don't stop at EEO_DONE here because we need to
+ 		 * resolve that opcode. Instead, depend on the NULL nextstep to
+ 		 * indicate the end of the list.
+ 		 */
+ 		for (op = state->steps; op != NULL; op = op->nextstep)
  			op->opcode = EEO_OPCODE(op->opcode);
  
  		state->flags |= EEO_FLAG_DIRECT_THREADED;
  	}
***************
*** 395,439 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_INNER_FETCHSOME)
  		{
  			/* XXX: worthwhile to check tts_nvalid inline first? */
! 			slot_getsomeattrs(innerslot, op->d.fetch.last_var);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_OUTER_FETCHSOME)
  		{
! 			slot_getsomeattrs(outerslot, op->d.fetch.last_var);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCAN_FETCHSOME)
  		{
! 			slot_getsomeattrs(scanslot, op->d.fetch.last_var);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_INNER_VAR_FIRST)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/*
  			 * First time through, check whether attribute matches Var.  Might
  			 * not be ok anymore, due to schema changes.
  			 */
! 			CheckVarSlotCompatibility(innerslot, attnum + 1, op->d.var.vartype);
  
  			/* Skip that check on subsequent evaluations */
! 			op->opcode = EEO_OPCODE(EEOP_INNER_VAR);
  
  			/* FALL THROUGH to EEOP_INNER_VAR */
  		}
  
  		EEO_CASE(EEOP_INNER_VAR)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/*
  			 * Since we already extracted all referenced columns from the
--- 401,453 ----
  
  		EEO_CASE(EEOP_INNER_FETCHSOME)
  		{
+ 			DEF_SOP(ExprEvalStep_fetch);
+ 
  			/* XXX: worthwhile to check tts_nvalid inline first? */
! 			slot_getsomeattrs(innerslot, sop->last_var);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_OUTER_FETCHSOME)
  		{
! 			DEF_SOP(ExprEvalStep_fetch);
! 
! 			slot_getsomeattrs(outerslot, sop->last_var);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCAN_FETCHSOME)
  		{
! 			DEF_SOP(ExprEvalStep_fetch);
! 
! 			slot_getsomeattrs(scanslot, sop->last_var);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_INNER_VAR_FIRST)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/*
  			 * First time through, check whether attribute matches Var.  Might
  			 * not be ok anymore, due to schema changes.
  			 */
! 			CheckVarSlotCompatibility(innerslot, attnum + 1, sop->vartype);
  
  			/* Skip that check on subsequent evaluations */
! 			sop->opcode = EEO_OPCODE(EEOP_INNER_VAR);
  
  			/* FALL THROUGH to EEOP_INNER_VAR */
  		}
  
  		EEO_CASE(EEOP_INNER_VAR)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/*
  			 * Since we already extracted all referenced columns from the
***************
*** 442,563 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			 * have an Assert to check that that did happen.
  			 */
  			Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
! 			*op->resvalue = innerslot->tts_values[attnum];
! 			*op->resnull = innerslot->tts_isnull[attnum];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_OUTER_VAR_FIRST)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* See EEOP_INNER_VAR_FIRST comments */
  
! 			CheckVarSlotCompatibility(outerslot, attnum + 1, op->d.var.vartype);
! 			op->opcode = EEO_OPCODE(EEOP_OUTER_VAR);
  
  			/* FALL THROUGH to EEOP_OUTER_VAR */
  		}
  
  		EEO_CASE(EEOP_OUTER_VAR)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* See EEOP_INNER_VAR comments */
  
  			Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
! 			*op->resvalue = outerslot->tts_values[attnum];
! 			*op->resnull = outerslot->tts_isnull[attnum];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCAN_VAR_FIRST)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* See EEOP_INNER_VAR_FIRST comments */
  
! 			CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype);
! 			op->opcode = EEO_OPCODE(EEOP_SCAN_VAR);
  
  			/* FALL THROUGH to EEOP_SCAN_VAR */
  		}
  
  		EEO_CASE(EEOP_SCAN_VAR)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* See EEOP_INNER_VAR comments */
  
  			Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
! 			*op->resvalue = scanslot->tts_values[attnum];
! 			*op->resnull = scanslot->tts_isnull[attnum];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_INNER_SYSVAR)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* these asserts must match defenses in slot_getattr */
  			Assert(innerslot->tts_tuple != NULL);
  			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
  			/* heap_getsysattr has sufficient defenses against bad attnums */
  
! 			*op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
  											innerslot->tts_tupleDescriptor,
! 											op->resnull);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_OUTER_SYSVAR)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* these asserts must match defenses in slot_getattr */
  			Assert(outerslot->tts_tuple != NULL);
  			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
  
  			/* heap_getsysattr has sufficient defenses against bad attnums */
! 			*op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
  											outerslot->tts_tupleDescriptor,
! 											op->resnull);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCAN_SYSVAR)
  		{
! 			int			attnum = op->d.var.attnum;
  
  			/* these asserts must match defenses in slot_getattr */
  			Assert(scanslot->tts_tuple != NULL);
  			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
  			/* heap_getsysattr has sufficient defenses against bad attnums */
  
! 			*op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
  											scanslot->tts_tupleDescriptor,
! 											op->resnull);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_WHOLEROW)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalWholeRowVar(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ASSIGN_INNER_VAR)
  		{
! 			int			resultnum = op->d.assign_var.resultnum;
! 			int			attnum = op->d.assign_var.attnum;
  
  			/*
  			 * We do not need CheckVarSlotCompatibility here; that was taken
--- 456,588 ----
  			 * have an Assert to check that that did happen.
  			 */
  			Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
! 			*sop->resvalue = innerslot->tts_values[attnum];
! 			*sop->resnull = innerslot->tts_isnull[attnum];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_OUTER_VAR_FIRST)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* See EEOP_INNER_VAR_FIRST comments */
  
! 			CheckVarSlotCompatibility(outerslot, attnum + 1, sop->vartype);
! 			sop->opcode = EEO_OPCODE(EEOP_OUTER_VAR);
  
  			/* FALL THROUGH to EEOP_OUTER_VAR */
  		}
  
  		EEO_CASE(EEOP_OUTER_VAR)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* See EEOP_INNER_VAR comments */
  
  			Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
! 			*sop->resvalue = outerslot->tts_values[attnum];
! 			*sop->resnull = outerslot->tts_isnull[attnum];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCAN_VAR_FIRST)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* See EEOP_INNER_VAR_FIRST comments */
  
! 			CheckVarSlotCompatibility(scanslot, attnum + 1, sop->vartype);
! 			sop->opcode = EEO_OPCODE(EEOP_SCAN_VAR);
  
  			/* FALL THROUGH to EEOP_SCAN_VAR */
  		}
  
  		EEO_CASE(EEOP_SCAN_VAR)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* See EEOP_INNER_VAR comments */
  
  			Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
! 			*sop->resvalue = scanslot->tts_values[attnum];
! 			*sop->resnull = scanslot->tts_isnull[attnum];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_INNER_SYSVAR)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* these asserts must match defenses in slot_getattr */
  			Assert(innerslot->tts_tuple != NULL);
  			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
  			/* heap_getsysattr has sufficient defenses against bad attnums */
  
! 			*sop->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
  											innerslot->tts_tupleDescriptor,
! 											sop->resnull);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_OUTER_SYSVAR)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* these asserts must match defenses in slot_getattr */
  			Assert(outerslot->tts_tuple != NULL);
  			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
  
  			/* heap_getsysattr has sufficient defenses against bad attnums */
! 			*sop->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
  											outerslot->tts_tupleDescriptor,
! 											sop->resnull);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCAN_SYSVAR)
  		{
! 			DEF_SOP(ExprEvalStep_var);
! 			int			attnum = sop->attnum;
  
  			/* these asserts must match defenses in slot_getattr */
  			Assert(scanslot->tts_tuple != NULL);
  			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
  			/* heap_getsysattr has sufficient defenses against bad attnums */
  
! 			*sop->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
  											scanslot->tts_tupleDescriptor,
! 											sop->resnull);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_WHOLEROW)
  		{
+ 			DEF_SOP(ExprEvalStep_wholerow);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalWholeRowVar(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ASSIGN_INNER_VAR)
  		{
! 			DEF_SOP(ExprEvalStep_assign_var);
! 
! 			int			resultnum = sop->resultnum;
! 			int			attnum = sop->attnum;
  
  			/*
  			 * We do not need CheckVarSlotCompatibility here; that was taken
***************
*** 572,579 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_ASSIGN_OUTER_VAR)
  		{
! 			int			resultnum = op->d.assign_var.resultnum;
! 			int			attnum = op->d.assign_var.attnum;
  
  			/*
  			 * We do not need CheckVarSlotCompatibility here; that was taken
--- 597,605 ----
  
  		EEO_CASE(EEOP_ASSIGN_OUTER_VAR)
  		{
! 			DEF_SOP(ExprEvalStep_assign_var);
! 			int			resultnum = sop->resultnum;
! 			int			attnum = sop->attnum;
  
  			/*
  			 * We do not need CheckVarSlotCompatibility here; that was taken
***************
*** 588,595 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_ASSIGN_SCAN_VAR)
  		{
! 			int			resultnum = op->d.assign_var.resultnum;
! 			int			attnum = op->d.assign_var.attnum;
  
  			/*
  			 * We do not need CheckVarSlotCompatibility here; that was taken
--- 614,622 ----
  
  		EEO_CASE(EEOP_ASSIGN_SCAN_VAR)
  		{
! 			DEF_SOP(ExprEvalStep_assign_var);
! 			int			resultnum = sop->resultnum;
! 			int			attnum = sop->attnum;
  
  			/*
  			 * We do not need CheckVarSlotCompatibility here; that was taken
***************
*** 604,610 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_ASSIGN_TMP)
  		{
! 			int			resultnum = op->d.assign_tmp.resultnum;
  
  			resultslot->tts_values[resultnum] = state->resvalue;
  			resultslot->tts_isnull[resultnum] = state->resnull;
--- 631,638 ----
  
  		EEO_CASE(EEOP_ASSIGN_TMP)
  		{
! 			DEF_SOP(ExprEvalStep_assign_tmp);
! 			int			resultnum = sop->resultnum;
  
  			resultslot->tts_values[resultnum] = state->resvalue;
  			resultslot->tts_isnull[resultnum] = state->resnull;
***************
*** 614,620 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO)
  		{
! 			int			resultnum = op->d.assign_tmp.resultnum;
  
  			resultslot->tts_isnull[resultnum] = state->resnull;
  			if (!resultslot->tts_isnull[resultnum])
--- 642,649 ----
  
  		EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO)
  		{
! 			DEF_SOP(ExprEvalStep_assign_tmp);
! 			int			resultnum = sop->resultnum;
  
  			resultslot->tts_isnull[resultnum] = state->resnull;
  			if (!resultslot->tts_isnull[resultnum])
***************
*** 628,635 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_CONST)
  		{
! 			*op->resnull = op->d.constval.isnull;
! 			*op->resvalue = op->d.constval.value;
  
  			EEO_NEXT();
  		}
--- 657,666 ----
  
  		EEO_CASE(EEOP_CONST)
  		{
! 			DEF_SOP(ExprEvalStep_constval);
! 
! 			*sop->resnull = sop->isnull;
! 			*sop->resvalue = sop->value;
  
  			EEO_NEXT();
  		}
***************
*** 644,676 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  		 */
  		EEO_CASE(EEOP_FUNCEXPR)
  		{
! 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
  
  			fcinfo->isnull = false;
! 			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
! 			*op->resnull = fcinfo->isnull;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FUNCEXPR_STRICT)
  		{
! 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
  			bool	   *argnull = fcinfo->argnull;
  			int			argno;
  
  			/* strict function, so check for NULL args */
! 			for (argno = 0; argno < op->d.func.nargs; argno++)
  			{
  				if (argnull[argno])
  				{
! 					*op->resnull = true;
  					goto strictfail;
  				}
  			}
  			fcinfo->isnull = false;
! 			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
! 			*op->resnull = fcinfo->isnull;
  
  	strictfail:
  			EEO_NEXT();
--- 675,709 ----
  		 */
  		EEO_CASE(EEOP_FUNCEXPR)
  		{
! 			DEF_SOP(ExprEvalStep_func);
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  
  			fcinfo->isnull = false;
! 			*sop->resvalue = (sop->fn_addr) (fcinfo);
! 			*sop->resnull = fcinfo->isnull;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FUNCEXPR_STRICT)
  		{
! 			DEF_SOP(ExprEvalStep_func);
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  			bool	   *argnull = fcinfo->argnull;
  			int			argno;
  
  			/* strict function, so check for NULL args */
! 			for (argno = 0; argno < sop->nargs; argno++)
  			{
  				if (argnull[argno])
  				{
! 					*sop->resnull = true;
  					goto strictfail;
  				}
  			}
  			fcinfo->isnull = false;
! 			*sop->resvalue = (sop->fn_addr) (fcinfo);
! 			*sop->resnull = fcinfo->isnull;
  
  	strictfail:
  			EEO_NEXT();
***************
*** 678,691 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_FUNCEXPR_FUSAGE)
  		{
! 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
  			PgStat_FunctionCallUsage fcusage;
  
  			pgstat_init_function_usage(fcinfo, &fcusage);
  
  			fcinfo->isnull = false;
! 			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
! 			*op->resnull = fcinfo->isnull;
  
  			pgstat_end_function_usage(&fcusage, true);
  
--- 711,725 ----
  
  		EEO_CASE(EEOP_FUNCEXPR_FUSAGE)
  		{
! 			DEF_SOP(ExprEvalStep_func);
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  			PgStat_FunctionCallUsage fcusage;
  
  			pgstat_init_function_usage(fcinfo, &fcusage);
  
  			fcinfo->isnull = false;
! 			*sop->resvalue = (sop->fn_addr) (fcinfo);
! 			*sop->resnull = fcinfo->isnull;
  
  			pgstat_end_function_usage(&fcusage, true);
  
***************
*** 694,710 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE)
  		{
! 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
  			PgStat_FunctionCallUsage fcusage;
  			bool	   *argnull = fcinfo->argnull;
  			int			argno;
  
  			/* strict function, so check for NULL args */
! 			for (argno = 0; argno < op->d.func.nargs; argno++)
  			{
  				if (argnull[argno])
  				{
! 					*op->resnull = true;
  					goto strictfail_fusage;
  				}
  			}
--- 728,745 ----
  
  		EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE)
  		{
! 			DEF_SOP(ExprEvalStep_func);
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  			PgStat_FunctionCallUsage fcusage;
  			bool	   *argnull = fcinfo->argnull;
  			int			argno;
  
  			/* strict function, so check for NULL args */
! 			for (argno = 0; argno < sop->nargs; argno++)
  			{
  				if (argnull[argno])
  				{
! 					*sop->resnull = true;
  					goto strictfail_fusage;
  				}
  			}
***************
*** 712,719 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			pgstat_init_function_usage(fcinfo, &fcusage);
  
  			fcinfo->isnull = false;
! 			*op->resvalue = (op->d.func.fn_addr) (fcinfo);
! 			*op->resnull = fcinfo->isnull;
  
  			pgstat_end_function_usage(&fcusage, true);
  
--- 747,754 ----
  			pgstat_init_function_usage(fcinfo, &fcusage);
  
  			fcinfo->isnull = false;
! 			*sop->resvalue = (sop->fn_addr) (fcinfo);
! 			*sop->resnull = fcinfo->isnull;
  
  			pgstat_end_function_usage(&fcusage, true);
  
***************
*** 733,739 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  		 */
  		EEO_CASE(EEOP_BOOL_AND_STEP_FIRST)
  		{
! 			*op->d.boolexpr.anynull = false;
  
  			/*
  			 * EEOP_BOOL_AND_STEP_FIRST resets anynull, otherwise it's the
--- 768,776 ----
  		 */
  		EEO_CASE(EEOP_BOOL_AND_STEP_FIRST)
  		{
! 			DEF_SOP(ExprEvalStep_boolexpr);
! 
! 			*sop->anynull = false;
  
  			/*
  			 * EEOP_BOOL_AND_STEP_FIRST resets anynull, otherwise it's the
***************
*** 745,759 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOL_AND_STEP)
  		{
! 			if (*op->resnull)
  			{
! 				*op->d.boolexpr.anynull = true;
  			}
! 			else if (!DatumGetBool(*op->resvalue))
  			{
  				/* result is already set to FALSE, need not change it */
  				/* bail out early */
! 				EEO_JUMP(op->d.boolexpr.jumpdone);
  			}
  
  			EEO_NEXT();
--- 782,798 ----
  
  		EEO_CASE(EEOP_BOOL_AND_STEP)
  		{
! 			DEF_SOP(ExprEvalStep_boolexpr);
! 
! 			if (*sop->resnull)
  			{
! 				*sop->anynull = true;
  			}
! 			else if (!DatumGetBool(*sop->resvalue))
  			{
  				/* result is already set to FALSE, need not change it */
  				/* bail out early */
! 				EEO_JUMP(sop->jumpdone);
  			}
  
  			EEO_NEXT();
***************
*** 761,771 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOL_AND_STEP_LAST)
  		{
! 			if (*op->resnull)
  			{
  				/* result is already set to NULL, need not change it */
  			}
! 			else if (!DatumGetBool(*op->resvalue))
  			{
  				/* result is already set to FALSE, need not change it */
  
--- 800,812 ----
  
  		EEO_CASE(EEOP_BOOL_AND_STEP_LAST)
  		{
! 			DEF_SOP(ExprEvalStep_boolexpr);
! 
! 			if (*sop->resnull)
  			{
  				/* result is already set to NULL, need not change it */
  			}
! 			else if (!DatumGetBool(*sop->resvalue))
  			{
  				/* result is already set to FALSE, need not change it */
  
***************
*** 775,784 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  				 * except more expensive.
  				 */
  			}
! 			else if (*op->d.boolexpr.anynull)
  			{
! 				*op->resvalue = (Datum) 0;
! 				*op->resnull = true;
  			}
  			else
  			{
--- 816,825 ----
  				 * except more expensive.
  				 */
  			}
! 			else if (*sop->anynull)
  			{
! 				*sop->resvalue = (Datum) 0;
! 				*sop->resnull = true;
  			}
  			else
  			{
***************
*** 800,806 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  		 */
  		EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
  		{
! 			*op->d.boolexpr.anynull = false;
  
  			/*
  			 * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same
--- 841,849 ----
  		 */
  		EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
  		{
! 			DEF_SOP(ExprEvalStep_boolexpr);
! 
! 			*sop->anynull = false;
  
  			/*
  			 * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same
***************
*** 812,826 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOL_OR_STEP)
  		{
! 			if (*op->resnull)
  			{
! 				*op->d.boolexpr.anynull = true;
  			}
! 			else if (DatumGetBool(*op->resvalue))
  			{
  				/* result is already set to TRUE, need not change it */
  				/* bail out early */
! 				EEO_JUMP(op->d.boolexpr.jumpdone);
  			}
  
  			EEO_NEXT();
--- 855,871 ----
  
  		EEO_CASE(EEOP_BOOL_OR_STEP)
  		{
! 			DEF_SOP(ExprEvalStep_boolexpr);
! 
! 			if (*sop->resnull)
  			{
! 				*sop->anynull = true;
  			}
! 			else if (DatumGetBool(*sop->resvalue))
  			{
  				/* result is already set to TRUE, need not change it */
  				/* bail out early */
! 				EEO_JUMP(sop->jumpdone);
  			}
  
  			EEO_NEXT();
***************
*** 828,838 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
  		{
! 			if (*op->resnull)
  			{
  				/* result is already set to NULL, need not change it */
  			}
! 			else if (DatumGetBool(*op->resvalue))
  			{
  				/* result is already set to TRUE, need not change it */
  
--- 873,885 ----
  
  		EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
  		{
! 			DEF_SOP(ExprEvalStep_boolexpr);
! 
! 			if (*sop->resnull)
  			{
  				/* result is already set to NULL, need not change it */
  			}
! 			else if (DatumGetBool(*sop->resvalue))
  			{
  				/* result is already set to TRUE, need not change it */
  
***************
*** 842,851 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  				 * more expensive.
  				 */
  			}
! 			else if (*op->d.boolexpr.anynull)
  			{
! 				*op->resvalue = (Datum) 0;
! 				*op->resnull = true;
  			}
  			else
  			{
--- 889,898 ----
  				 * more expensive.
  				 */
  			}
! 			else if (*sop->anynull)
  			{
! 				*sop->resvalue = (Datum) 0;
! 				*sop->resnull = true;
  			}
  			else
  			{
***************
*** 857,885 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOL_NOT_STEP)
  		{
  			/*
  			 * Evaluation of 'not' is simple... if expr is false, then return
  			 * 'true' and vice versa.  It's safe to do this even on a
  			 * nominally null value, so we ignore resnull; that means that
  			 * NULL in produces NULL out, which is what we want.
  			 */
! 			*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_QUAL)
  		{
  			/* simplified version of BOOL_AND_STEP for use by ExecQual() */
  
  			/* If argument (also result) is false or null ... */
! 			if (*op->resnull ||
! 				!DatumGetBool(*op->resvalue))
  			{
  				/* ... bail out early, returning FALSE */
! 				*op->resnull = false;
! 				*op->resvalue = BoolGetDatum(false);
! 				EEO_JUMP(op->d.qualexpr.jumpdone);
  			}
  
  			/*
--- 904,936 ----
  
  		EEO_CASE(EEOP_BOOL_NOT_STEP)
  		{
+ 			DEF_SOP(ExprEvalStep_boolexpr);
+ 
  			/*
  			 * Evaluation of 'not' is simple... if expr is false, then return
  			 * 'true' and vice versa.  It's safe to do this even on a
  			 * nominally null value, so we ignore resnull; that means that
  			 * NULL in produces NULL out, which is what we want.
  			 */
! 			*sop->resvalue = BoolGetDatum(!DatumGetBool(*sop->resvalue));
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_QUAL)
  		{
+ 			DEF_SOP(ExprEvalStep_qualexpr);
+ 
  			/* simplified version of BOOL_AND_STEP for use by ExecQual() */
  
  			/* If argument (also result) is false or null ... */
! 			if (*sop->resnull ||
! 				!DatumGetBool(*sop->resvalue))
  			{
  				/* ... bail out early, returning FALSE */
! 				*sop->resnull = false;
! 				*sop->resvalue = BoolGetDatum(false);
! 				EEO_JUMP(sop->jumpdone);
  			}
  
  			/*
***************
*** 892,956 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_JUMP)
  		{
  			/* Unconditionally jump to target step */
! 			EEO_JUMP(op->d.jump.jumpdone);
  		}
  
  		EEO_CASE(EEOP_JUMP_IF_NULL)
  		{
  			/* Transfer control if current result is null */
! 			if (*op->resnull)
! 				EEO_JUMP(op->d.jump.jumpdone);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_JUMP_IF_NOT_NULL)
  		{
  			/* Transfer control if current result is non-null */
! 			if (!*op->resnull)
! 				EEO_JUMP(op->d.jump.jumpdone);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
  		{
  			/* Transfer control if current result is null or false */
! 			if (*op->resnull || !DatumGetBool(*op->resvalue))
! 				EEO_JUMP(op->d.jump.jumpdone);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ISNULL)
  		{
! 			*op->resvalue = BoolGetDatum(*op->resnull);
! 			*op->resnull = false;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ISNOTNULL)
  		{
! 			*op->resvalue = BoolGetDatum(!*op->resnull);
! 			*op->resnull = false;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ROWISNULL)
  		{
  			/* out of line implementation: too large */
! 			ExecEvalRowNull(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL)
  		{
  			/* out of line implementation: too large */
! 			ExecEvalRowNotNull(state, op, econtext);
  
  			EEO_NEXT();
  		}
--- 943,1023 ----
  
  		EEO_CASE(EEOP_JUMP)
  		{
+ 			DEF_SOP(ExprEvalStep_jump);
+ 
  			/* Unconditionally jump to target step */
! 			EEO_JUMP(sop->jumpdone);
  		}
  
  		EEO_CASE(EEOP_JUMP_IF_NULL)
  		{
+ 			DEF_SOP(ExprEvalStep_jump);
+ 
  			/* Transfer control if current result is null */
! 			if (*sop->resnull)
! 				EEO_JUMP(sop->jumpdone);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_JUMP_IF_NOT_NULL)
  		{
+ 			DEF_SOP(ExprEvalStep_jump);
+ 
  			/* Transfer control if current result is non-null */
! 			if (!*sop->resnull)
! 				EEO_JUMP(sop->jumpdone);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
  		{
+ 			DEF_SOP(ExprEvalStep_jump);
+ 
  			/* Transfer control if current result is null or false */
! 			if (*sop->resnull || !DatumGetBool(*sop->resvalue))
! 				EEO_JUMP(sop->jumpdone);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ISNULL)
  		{
! 			DEF_SOP(ExprEvalStep_nulltest_row);
! 
! 			*sop->resvalue = BoolGetDatum(*sop->resnull);
! 			*sop->resnull = false;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ISNOTNULL)
  		{
! 			DEF_SOP(ExprEvalStep_nulltest_row);
! 
! 			*sop->resvalue = BoolGetDatum(!*sop->resnull);
! 			*sop->resnull = false;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ROWISNULL)
  		{
+ 			DEF_SOP(ExprEvalStep_nulltest_row);
+ 
  			/* out of line implementation: too large */
! 			ExecEvalRowNull(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL)
  		{
+ 			DEF_SOP(ExprEvalStep_nulltest_row);
+ 
  			/* out of line implementation: too large */
! 			ExecEvalRowNotNull(state, sop, econtext);
  
  			EEO_NEXT();
  		}
***************
*** 959,968 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOLTEST_IS_TRUE)
  		{
! 			if (*op->resnull)
  			{
! 				*op->resvalue = BoolGetDatum(false);
! 				*op->resnull = false;
  			}
  			/* else, input value is the correct output as well */
  
--- 1026,1037 ----
  
  		EEO_CASE(EEOP_BOOLTEST_IS_TRUE)
  		{
! 			DEF_SOP(ExprEvalStep);
! 
! 			if (*sop->resnull)
  			{
! 				*sop->resvalue = BoolGetDatum(false);
! 				*sop->resnull = false;
  			}
  			/* else, input value is the correct output as well */
  
***************
*** 971,1006 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE)
  		{
! 			if (*op->resnull)
  			{
! 				*op->resvalue = BoolGetDatum(true);
! 				*op->resnull = false;
  			}
  			else
! 				*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_BOOLTEST_IS_FALSE)
  		{
! 			if (*op->resnull)
  			{
! 				*op->resvalue = BoolGetDatum(false);
! 				*op->resnull = false;
  			}
  			else
! 				*op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE)
  		{
! 			if (*op->resnull)
  			{
! 				*op->resvalue = BoolGetDatum(true);
! 				*op->resnull = false;
  			}
  			/* else, input value is the correct output as well */
  
--- 1040,1081 ----
  
  		EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE)
  		{
! 			DEF_SOP(ExprEvalStep);
! 
! 			if (*sop->resnull)
  			{
! 				*sop->resvalue = BoolGetDatum(true);
! 				*sop->resnull = false;
  			}
  			else
! 				*sop->resvalue = BoolGetDatum(!DatumGetBool(*sop->resvalue));
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_BOOLTEST_IS_FALSE)
  		{
! 			DEF_SOP(ExprEvalStep);
! 
! 			if (*sop->resnull)
  			{
! 				*sop->resvalue = BoolGetDatum(false);
! 				*sop->resnull = false;
  			}
  			else
! 				*sop->resvalue = BoolGetDatum(!DatumGetBool(*sop->resvalue));
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE)
  		{
! 			DEF_SOP(ExprEvalStep);
! 
! 			if (*sop->resnull)
  			{
! 				*sop->resvalue = BoolGetDatum(true);
! 				*sop->resnull = false;
  			}
  			/* else, input value is the correct output as well */
  
***************
*** 1009,1029 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_PARAM_EXEC)
  		{
  			/* out of line implementation: too large */
! 			ExecEvalParamExec(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_PARAM_EXTERN)
  		{
  			/* out of line implementation: too large */
! 			ExecEvalParamExtern(state, op, econtext);
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_CASE_TESTVAL)
  		{
  			/*
  			 * Normally upper parts of the expression tree have setup the
  			 * values to be returned here, but some parts of the system
--- 1084,1110 ----
  
  		EEO_CASE(EEOP_PARAM_EXEC)
  		{
+ 			DEF_SOP(ExprEvalStep_param);
+ 
  			/* out of line implementation: too large */
! 			ExecEvalParamExec(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_PARAM_EXTERN)
  		{
+ 			DEF_SOP(ExprEvalStep_param);
+ 
  			/* out of line implementation: too large */
! 			ExecEvalParamExtern(state, sop, econtext);
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_CASE_TESTVAL)
  		{
+ 			DEF_SOP(ExprEvalStep_casetest);
+ 
  			/*
  			 * Normally upper parts of the expression tree have setup the
  			 * values to be returned here, but some parts of the system
***************
*** 1033,1047 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			 * and this is unlikely to be performance sensitive enough to
  			 * worry about an extra branch.
  			 */
! 			if (op->d.casetest.value)
  			{
! 				*op->resvalue = *op->d.casetest.value;
! 				*op->resnull = *op->d.casetest.isnull;
  			}
  			else
  			{
! 				*op->resvalue = econtext->caseValue_datum;
! 				*op->resnull = econtext->caseValue_isNull;
  			}
  
  			EEO_NEXT();
--- 1114,1128 ----
  			 * and this is unlikely to be performance sensitive enough to
  			 * worry about an extra branch.
  			 */
! 			if (sop->value)
  			{
! 				*sop->resvalue = *sop->value;
! 				*sop->resnull = *sop->isnull;
  			}
  			else
  			{
! 				*sop->resvalue = econtext->caseValue_datum;
! 				*sop->resnull = econtext->caseValue_isNull;
  			}
  
  			EEO_NEXT();
***************
*** 1049,1066 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_DOMAIN_TESTVAL)
  		{
  			/*
  			 * See EEOP_CASE_TESTVAL comment.
  			 */
! 			if (op->d.casetest.value)
  			{
! 				*op->resvalue = *op->d.casetest.value;
! 				*op->resnull = *op->d.casetest.isnull;
  			}
  			else
  			{
! 				*op->resvalue = econtext->domainValue_datum;
! 				*op->resnull = econtext->domainValue_isNull;
  			}
  
  			EEO_NEXT();
--- 1130,1149 ----
  
  		EEO_CASE(EEOP_DOMAIN_TESTVAL)
  		{
+ 			DEF_SOP(ExprEvalStep_casetest);
+ 
  			/*
  			 * See EEOP_CASE_TESTVAL comment.
  			 */
! 			if (sop->value)
  			{
! 				*sop->resvalue = *sop->value;
! 				*sop->resnull = *sop->isnull;
  			}
  			else
  			{
! 				*sop->resvalue = econtext->domainValue_datum;
! 				*sop->resnull = econtext->domainValue_isNull;
  			}
  
  			EEO_NEXT();
***************
*** 1068,1086 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_MAKE_READONLY)
  		{
  			/*
  			 * Force a varlena value that might be read multiple times to R/O
  			 */
! 			if (!*op->d.make_readonly.isnull)
! 				*op->resvalue =
! 					MakeExpandedObjectReadOnlyInternal(*op->d.make_readonly.value);
! 			*op->resnull = *op->d.make_readonly.isnull;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_IOCOERCE)
  		{
  			/*
  			 * Evaluate a CoerceViaIO node.  This can be quite a hot path, so
  			 * inline as much work as possible.  The source value is in our
--- 1151,1173 ----
  
  		EEO_CASE(EEOP_MAKE_READONLY)
  		{
+ 			DEF_SOP(ExprEvalStep_make_readonly);
+ 
  			/*
  			 * Force a varlena value that might be read multiple times to R/O
  			 */
! 			if (!*sop->isnull)
! 				*sop->resvalue =
! 					MakeExpandedObjectReadOnlyInternal(*sop->value);
! 			*sop->resnull = *sop->isnull;
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_IOCOERCE)
  		{
+ 			DEF_SOP(ExprEvalStep_iocoerce);
+ 
  			/*
  			 * Evaluate a CoerceViaIO node.  This can be quite a hot path, so
  			 * inline as much work as possible.  The source value is in our
***************
*** 1089,1095 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			char	   *str;
  
  			/* call output function (similar to OutputFunctionCall) */
! 			if (*op->resnull)
  			{
  				/* output functions are not called on nulls */
  				str = NULL;
--- 1176,1182 ----
  			char	   *str;
  
  			/* call output function (similar to OutputFunctionCall) */
! 			if (*sop->resnull)
  			{
  				/* output functions are not called on nulls */
  				str = NULL;
***************
*** 1098,1105 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			{
  				FunctionCallInfo fcinfo_out;
  
! 				fcinfo_out = op->d.iocoerce.fcinfo_data_out;
! 				fcinfo_out->arg[0] = *op->resvalue;
  				fcinfo_out->argnull[0] = false;
  
  				fcinfo_out->isnull = false;
--- 1185,1192 ----
  			{
  				FunctionCallInfo fcinfo_out;
  
! 				fcinfo_out = sop->fcinfo_data_out;
! 				fcinfo_out->arg[0] = *sop->resvalue;
  				fcinfo_out->argnull[0] = false;
  
  				fcinfo_out->isnull = false;
***************
*** 1110,1136 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			}
  
  			/* call input function (similar to InputFunctionCall) */
! 			if (!op->d.iocoerce.finfo_in->fn_strict || str != NULL)
  			{
  				FunctionCallInfo fcinfo_in;
  
! 				fcinfo_in = op->d.iocoerce.fcinfo_data_in;
  				fcinfo_in->arg[0] = PointerGetDatum(str);
! 				fcinfo_in->argnull[0] = *op->resnull;
  				/* second and third arguments are already set up */
  
  				fcinfo_in->isnull = false;
! 				*op->resvalue = FunctionCallInvoke(fcinfo_in);
  
  				/* Should get null result if and only if str is NULL */
  				if (str == NULL)
  				{
! 					Assert(*op->resnull);
  					Assert(fcinfo_in->isnull);
  				}
  				else
  				{
! 					Assert(!*op->resnull);
  					Assert(!fcinfo_in->isnull);
  				}
  			}
--- 1197,1223 ----
  			}
  
  			/* call input function (similar to InputFunctionCall) */
! 			if (!sop->finfo_in->fn_strict || str != NULL)
  			{
  				FunctionCallInfo fcinfo_in;
  
! 				fcinfo_in = sop->fcinfo_data_in;
  				fcinfo_in->arg[0] = PointerGetDatum(str);
! 				fcinfo_in->argnull[0] = *sop->resnull;
  				/* second and third arguments are already set up */
  
  				fcinfo_in->isnull = false;
! 				*sop->resvalue = FunctionCallInvoke(fcinfo_in);
  
  				/* Should get null result if and only if str is NULL */
  				if (str == NULL)
  				{
! 					Assert(*sop->resnull);
  					Assert(fcinfo_in->isnull);
  				}
  				else
  				{
! 					Assert(!*sop->resnull);
  					Assert(!fcinfo_in->isnull);
  				}
  			}
***************
*** 1140,1145 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
--- 1227,1234 ----
  
  		EEO_CASE(EEOP_DISTINCT)
  		{
+ 			DEF_SOP(ExprEvalStep_func);
+ 
  			/*
  			 * IS DISTINCT FROM must evaluate arguments (already done into
  			 * fcinfo->arg/argnull) to determine whether they are NULL; if
***************
*** 1149,1168 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			 * care whether that function is strict.  Because the handling of
  			 * nulls is different, we can't just reuse EEOP_FUNCEXPR.
  			 */
! 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
  
  			/* check function arguments for NULLness */
  			if (fcinfo->argnull[0] && fcinfo->argnull[1])
  			{
  				/* Both NULL? Then is not distinct... */
! 				*op->resvalue = BoolGetDatum(false);
! 				*op->resnull = false;
  			}
  			else if (fcinfo->argnull[0] || fcinfo->argnull[1])
  			{
  				/* Only one is NULL? Then is distinct... */
! 				*op->resvalue = BoolGetDatum(true);
! 				*op->resnull = false;
  			}
  			else
  			{
--- 1238,1257 ----
  			 * care whether that function is strict.  Because the handling of
  			 * nulls is different, we can't just reuse EEOP_FUNCEXPR.
  			 */
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  
  			/* check function arguments for NULLness */
  			if (fcinfo->argnull[0] && fcinfo->argnull[1])
  			{
  				/* Both NULL? Then is not distinct... */
! 				*sop->resvalue = BoolGetDatum(false);
! 				*sop->resnull = false;
  			}
  			else if (fcinfo->argnull[0] || fcinfo->argnull[1])
  			{
  				/* Only one is NULL? Then is distinct... */
! 				*sop->resvalue = BoolGetDatum(true);
! 				*sop->resnull = false;
  			}
  			else
  			{
***************
*** 1170,1179 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  				Datum		eqresult;
  
  				fcinfo->isnull = false;
! 				eqresult = (op->d.func.fn_addr) (fcinfo);
  				/* Must invert result of "="; safe to do even if null */
! 				*op->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
! 				*op->resnull = fcinfo->isnull;
  			}
  
  			EEO_NEXT();
--- 1259,1268 ----
  				Datum		eqresult;
  
  				fcinfo->isnull = false;
! 				eqresult = (sop->fn_addr) (fcinfo);
  				/* Must invert result of "="; safe to do even if null */
! 				*sop->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
! 				*sop->resnull = fcinfo->isnull;
  			}
  
  			EEO_NEXT();
***************
*** 1181,1190 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_NULLIF)
  		{
  			/*
  			 * The arguments are already evaluated into fcinfo->arg/argnull.
  			 */
! 			FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
  
  			/* if either argument is NULL they can't be equal */
  			if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
--- 1270,1281 ----
  
  		EEO_CASE(EEOP_NULLIF)
  		{
+ 			DEF_SOP(ExprEvalStep_func);
+ 
  			/*
  			 * The arguments are already evaluated into fcinfo->arg/argnull.
  			 */
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  
  			/* if either argument is NULL they can't be equal */
  			if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
***************
*** 1192,1298 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  				Datum		result;
  
  				fcinfo->isnull = false;
! 				result = (op->d.func.fn_addr) (fcinfo);
  
  				/* if the arguments are equal return null */
  				if (!fcinfo->isnull && DatumGetBool(result))
  				{
! 					*op->resvalue = (Datum) 0;
! 					*op->resnull = true;
  
  					EEO_NEXT();
  				}
  			}
  
  			/* Arguments aren't equal, so return the first one */
! 			*op->resvalue = fcinfo->arg[0];
! 			*op->resnull = fcinfo->argnull[0];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SQLVALUEFUNCTION)
  		{
  			/*
  			 * Doesn't seem worthwhile to have an inline implementation
  			 * efficiency-wise.
  			 */
! 			ExecEvalSQLValueFunction(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_CURRENTOFEXPR)
  		{
  			/* error invocation uses space, and shouldn't ever occur */
! 			ExecEvalCurrentOfExpr(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NEXTVALUEEXPR)
  		{
  			/*
  			 * Doesn't seem worthwhile to have an inline implementation
  			 * efficiency-wise.
  			 */
! 			ExecEvalNextValueExpr(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ARRAYEXPR)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalArrayExpr(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ARRAYCOERCE)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalArrayCoerce(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ROW)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalRow(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ROWCOMPARE_STEP)
  		{
! 			FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data;
  
  			/* force NULL result if strict fn and NULL input */
! 			if (op->d.rowcompare_step.finfo->fn_strict &&
  				(fcinfo->argnull[0] || fcinfo->argnull[1]))
  			{
! 				*op->resnull = true;
! 				EEO_JUMP(op->d.rowcompare_step.jumpnull);
  			}
  
  			/* Apply comparison function */
  			fcinfo->isnull = false;
! 			*op->resvalue = (op->d.rowcompare_step.fn_addr) (fcinfo);
  
  			/* force NULL result if NULL function result */
  			if (fcinfo->isnull)
  			{
! 				*op->resnull = true;
! 				EEO_JUMP(op->d.rowcompare_step.jumpnull);
  			}
! 			*op->resnull = false;
  
  			/* If unequal, no need to compare remaining columns */
! 			if (DatumGetInt32(*op->resvalue) != 0)
  			{
! 				EEO_JUMP(op->d.rowcompare_step.jumpdone);
  			}
  
  			EEO_NEXT();
--- 1283,1402 ----
  				Datum		result;
  
  				fcinfo->isnull = false;
! 				result = (sop->fn_addr) (fcinfo);
  
  				/* if the arguments are equal return null */
  				if (!fcinfo->isnull && DatumGetBool(result))
  				{
! 					*sop->resvalue = (Datum) 0;
! 					*sop->resnull = true;
  
  					EEO_NEXT();
  				}
  			}
  
  			/* Arguments aren't equal, so return the first one */
! 			*sop->resvalue = fcinfo->arg[0];
! 			*sop->resnull = fcinfo->argnull[0];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SQLVALUEFUNCTION)
  		{
+ 			DEF_SOP(ExprEvalStep_sqlvaluefunction);
+ 
  			/*
  			 * Doesn't seem worthwhile to have an inline implementation
  			 * efficiency-wise.
  			 */
! 			ExecEvalSQLValueFunction(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_CURRENTOFEXPR)
  		{
+ 			DEF_SOP(ExprEvalStep);
+ 
  			/* error invocation uses space, and shouldn't ever occur */
! 			ExecEvalCurrentOfExpr(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_NEXTVALUEEXPR)
  		{
+ 			DEF_SOP(ExprEvalStep_nextvalueexpr);
+ 
  			/*
  			 * Doesn't seem worthwhile to have an inline implementation
  			 * efficiency-wise.
  			 */
! 			ExecEvalNextValueExpr(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ARRAYEXPR)
  		{
+ 			DEF_SOP(ExprEvalStep_arrayexpr);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalArrayExpr(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ARRAYCOERCE)
  		{
+ 			DEF_SOP(ExprEvalStep_arraycoerce);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalArrayCoerce(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ROW)
  		{
+ 			DEF_SOP(ExprEvalStep_row);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalRow(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ROWCOMPARE_STEP)
  		{
! 			DEF_SOP(ExprEvalStep_rowcompare_step);
! 			FunctionCallInfo fcinfo = sop->fcinfo_data;
  
  			/* force NULL result if strict fn and NULL input */
! 			if (sop->finfo->fn_strict &&
  				(fcinfo->argnull[0] || fcinfo->argnull[1]))
  			{
! 				*sop->resnull = true;
! 				EEO_JUMP(sop->jumpnull);
  			}
  
  			/* Apply comparison function */
  			fcinfo->isnull = false;
! 			*sop->resvalue = (sop->fn_addr) (fcinfo);
  
  			/* force NULL result if NULL function result */
  			if (fcinfo->isnull)
  			{
! 				*sop->resnull = true;
! 				EEO_JUMP(sop->jumpnull);
  			}
! 			*sop->resnull = false;
  
  			/* If unequal, no need to compare remaining columns */
! 			if (DatumGetInt32(*sop->resvalue) != 0)
  			{
! 				EEO_JUMP(sop->jumpdone);
  			}
  
  			EEO_NEXT();
***************
*** 1300,1323 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_ROWCOMPARE_FINAL)
  		{
! 			int32		cmpresult = DatumGetInt32(*op->resvalue);
! 			RowCompareType rctype = op->d.rowcompare_final.rctype;
  
! 			*op->resnull = false;
  			switch (rctype)
  			{
  					/* EQ and NE cases aren't allowed here */
  				case ROWCOMPARE_LT:
! 					*op->resvalue = BoolGetDatum(cmpresult < 0);
  					break;
  				case ROWCOMPARE_LE:
! 					*op->resvalue = BoolGetDatum(cmpresult <= 0);
  					break;
  				case ROWCOMPARE_GE:
! 					*op->resvalue = BoolGetDatum(cmpresult >= 0);
  					break;
  				case ROWCOMPARE_GT:
! 					*op->resvalue = BoolGetDatum(cmpresult > 0);
  					break;
  				default:
  					Assert(false);
--- 1404,1428 ----
  
  		EEO_CASE(EEOP_ROWCOMPARE_FINAL)
  		{
! 			DEF_SOP(ExprEvalStep_rowcompare_final);
! 			int32		cmpresult = DatumGetInt32(*sop->resvalue);
! 			RowCompareType rctype = sop->rctype;
  
! 			*sop->resnull = false;
  			switch (rctype)
  			{
  					/* EQ and NE cases aren't allowed here */
  				case ROWCOMPARE_LT:
! 					*sop->resvalue = BoolGetDatum(cmpresult < 0);
  					break;
  				case ROWCOMPARE_LE:
! 					*sop->resvalue = BoolGetDatum(cmpresult <= 0);
  					break;
  				case ROWCOMPARE_GE:
! 					*sop->resvalue = BoolGetDatum(cmpresult >= 0);
  					break;
  				case ROWCOMPARE_GT:
! 					*sop->resvalue = BoolGetDatum(cmpresult > 0);
  					break;
  				default:
  					Assert(false);
***************
*** 1329,1382 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  
  		EEO_CASE(EEOP_MINMAX)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalMinMax(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FIELDSELECT)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalFieldSelect(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FIELDSTORE_DEFORM)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalFieldStoreDeForm(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FIELDSTORE_FORM)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalFieldStoreForm(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ARRAYREF_SUBSCRIPT)
  		{
  			/* Process an array subscript */
  
  			/* too complex for an inline implementation */
! 			if (ExecEvalArrayRefSubscript(state, op))
  			{
  				EEO_NEXT();
  			}
  			else
  			{
  				/* Subscript is null, short-circuit ArrayRef to NULL */
! 				EEO_JUMP(op->d.arrayref_subscript.jumpdone);
  			}
  		}
  
  		EEO_CASE(EEOP_ARRAYREF_OLD)
  		{
  			/*
  			 * Fetch the old value in an arrayref assignment, in case it's
  			 * referenced (via a CaseTestExpr) inside the assignment
--- 1434,1499 ----
  
  		EEO_CASE(EEOP_MINMAX)
  		{
+ 			DEF_SOP(ExprEvalStep_minmax);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalMinMax(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FIELDSELECT)
  		{
+ 			DEF_SOP(ExprEvalStep_fieldselect);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalFieldSelect(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FIELDSTORE_DEFORM)
  		{
+ 			DEF_SOP(ExprEvalStep_fieldstore);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalFieldStoreDeForm(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_FIELDSTORE_FORM)
  		{
+ 			DEF_SOP(ExprEvalStep_fieldstore);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalFieldStoreForm(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ARRAYREF_SUBSCRIPT)
  		{
+ 			DEF_SOP(ExprEvalStep_arrayref_subscript);
+ 
  			/* Process an array subscript */
  
  			/* too complex for an inline implementation */
! 			if (ExecEvalArrayRefSubscript(state, sop))
  			{
  				EEO_NEXT();
  			}
  			else
  			{
  				/* Subscript is null, short-circuit ArrayRef to NULL */
! 				EEO_JUMP(sop->jumpdone);
  			}
  		}
  
  		EEO_CASE(EEOP_ARRAYREF_OLD)
  		{
+ 			DEF_SOP(ExprEvalStep_arrayref);
+ 
  			/*
  			 * Fetch the old value in an arrayref assignment, in case it's
  			 * referenced (via a CaseTestExpr) inside the assignment
***************
*** 1384,1390 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			 */
  
  			/* too complex for an inline implementation */
! 			ExecEvalArrayRefOld(state, op);
  
  			EEO_NEXT();
  		}
--- 1501,1507 ----
  			 */
  
  			/* too complex for an inline implementation */
! 			ExecEvalArrayRefOld(state, sop);
  
  			EEO_NEXT();
  		}
***************
*** 1394,1401 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  		 */
  		EEO_CASE(EEOP_ARRAYREF_ASSIGN)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalArrayRefAssign(state, op);
  
  			EEO_NEXT();
  		}
--- 1511,1520 ----
  		 */
  		EEO_CASE(EEOP_ARRAYREF_ASSIGN)
  		{
+ 			DEF_SOP(ExprEvalStep_arrayref);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalArrayRefAssign(state, sop);
  
  			EEO_NEXT();
  		}
***************
*** 1405,1452 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  		 */
  		EEO_CASE(EEOP_ARRAYREF_FETCH)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalArrayRefFetch(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_CONVERT_ROWTYPE)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalConvertRowtype(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCALARARRAYOP)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalScalarArrayOp(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_DOMAIN_NOTNULL)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalConstraintNotNull(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_DOMAIN_CHECK)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalConstraintCheck(state, op);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_XMLEXPR)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalXmlExpr(state, op);
  
  			EEO_NEXT();
  		}
--- 1524,1583 ----
  		 */
  		EEO_CASE(EEOP_ARRAYREF_FETCH)
  		{
+ 			DEF_SOP(ExprEvalStep_arrayref);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalArrayRefFetch(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_CONVERT_ROWTYPE)
  		{
+ 			DEF_SOP(ExprEvalStep_convert_rowtype);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalConvertRowtype(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SCALARARRAYOP)
  		{
+ 			DEF_SOP(ExprEvalStep_scalararrayop);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalScalarArrayOp(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_DOMAIN_NOTNULL)
  		{
+ 			DEF_SOP(ExprEvalStep_domaincheck);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalConstraintNotNull(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_DOMAIN_CHECK)
  		{
+ 			DEF_SOP(ExprEvalStep_domaincheck);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalConstraintCheck(state, sop);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_XMLEXPR)
  		{
+ 			DEF_SOP(ExprEvalStep_xmlexpr);
+ 
  			/* too complex for an inline implementation */
! 			ExecEvalXmlExpr(state, sop);
  
  			EEO_NEXT();
  		}
***************
*** 1457,1476 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			 * Returns a Datum whose value is the precomputed aggregate value
  			 * found in the given expression context.
  			 */
! 			AggrefExprState *aggref = op->d.aggref.astate;
  
  			Assert(econtext->ecxt_aggvalues != NULL);
  
! 			*op->resvalue = econtext->ecxt_aggvalues[aggref->aggno];
! 			*op->resnull = econtext->ecxt_aggnulls[aggref->aggno];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_GROUPING_FUNC)
  		{
  			/* too complex/uncommon for an inline implementation */
! 			ExecEvalGroupingFunc(state, op);
  
  			EEO_NEXT();
  		}
--- 1588,1610 ----
  			 * Returns a Datum whose value is the precomputed aggregate value
  			 * found in the given expression context.
  			 */
! 			DEF_SOP(ExprEvalStep_aggref);
! 			AggrefExprState *aggref = sop->astate;
  
  			Assert(econtext->ecxt_aggvalues != NULL);
  
! 			*sop->resvalue = econtext->ecxt_aggvalues[aggref->aggno];
! 			*sop->resnull = econtext->ecxt_aggnulls[aggref->aggno];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_GROUPING_FUNC)
  		{
+ 			DEF_SOP(ExprEvalStep_grouping_func);
+ 
  			/* too complex/uncommon for an inline implementation */
! 			ExecEvalGroupingFunc(state, sop);
  
  			EEO_NEXT();
  		}
***************
*** 1480,1507 **** ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
  			/*
  			 * Like Aggref, just return a precomputed value from the econtext.
  			 */
! 			WindowFuncExprState *wfunc = op->d.window_func.wfstate;
  
  			Assert(econtext->ecxt_aggvalues != NULL);
  
! 			*op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno];
! 			*op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SUBPLAN)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalSubPlan(state, op, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN)
  		{
  			/* too complex for an inline implementation */
! 			ExecEvalAlternativeSubPlan(state, op, econtext);
  
  			EEO_NEXT();
  		}
--- 1614,1646 ----
  			/*
  			 * Like Aggref, just return a precomputed value from the econtext.
  			 */
! 			DEF_SOP(ExprEvalStep_window_func);
! 			WindowFuncExprState *wfunc = sop->wfstate;
  
  			Assert(econtext->ecxt_aggvalues != NULL);
  
! 			*sop->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno];
! 			*sop->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno];
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_SUBPLAN)
  		{
+ 			DEF_SOP(ExprEvalStep_subplan);
+ 			
  			/* too complex for an inline implementation */
! 			ExecEvalSubPlan(state, sop, econtext);
  
  			EEO_NEXT();
  		}
  
  		EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN)
  		{
+ 			DEF_SOP(ExprEvalStep_alternative_subplan);
+ 			
  			/* too complex for an inline implementation */
! 			ExecEvalAlternativeSubPlan(state, sop, econtext);
  
  			EEO_NEXT();
  		}
***************
*** 1635,1648 **** ShutdownTupleDescRef(Datum arg)
  static Datum
  ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.var.attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_innertuple;
  
  	/* See ExecInterpExpr()'s comments for EEOP_INNER_VAR_FIRST */
  
! 	CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
! 	op->opcode = EEOP_INNER_VAR;	/* just for cleanliness */
  	state->evalfunc = ExecJustInnerVar;
  
  	/*
--- 1774,1788 ----
  static Datum
  ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_var);
! 	int			attnum = sop->attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_innertuple;
  
  	/* See ExecInterpExpr()'s comments for EEOP_INNER_VAR_FIRST */
  
! 	CheckVarSlotCompatibility(slot, attnum, sop->vartype);
! 	sop->opcode = EEOP_INNER_VAR;	/* just for cleanliness */
  	state->evalfunc = ExecJustInnerVar;
  
  	/*
***************
*** 1657,1664 **** ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.var.attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_innertuple;
  
  	/* See comments in ExecJustInnerVarFirst */
--- 1797,1805 ----
  static Datum
  ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_var);
! 	int			attnum = sop->attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_innertuple;
  
  	/* See comments in ExecJustInnerVarFirst */
***************
*** 1669,1680 **** ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.var.attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_outertuple;
  
! 	CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
! 	op->opcode = EEOP_OUTER_VAR;	/* just for cleanliness */
  	state->evalfunc = ExecJustOuterVar;
  
  	/* See comments in ExecJustInnerVarFirst */
--- 1810,1822 ----
  static Datum
  ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_var);
! 	int			attnum = sop->attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_outertuple;
  
! 	CheckVarSlotCompatibility(slot, attnum, sop->vartype);
! 	sop->opcode = EEOP_OUTER_VAR;	/* just for cleanliness */
  	state->evalfunc = ExecJustOuterVar;
  
  	/* See comments in ExecJustInnerVarFirst */
***************
*** 1685,1692 **** ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.var.attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_outertuple;
  
  	/* See comments in ExecJustInnerVarFirst */
--- 1827,1835 ----
  static Datum
  ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_var);
! 	int			attnum = sop->attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_outertuple;
  
  	/* See comments in ExecJustInnerVarFirst */
***************
*** 1697,1708 **** ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.var.attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_scantuple;
  
! 	CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
! 	op->opcode = EEOP_SCAN_VAR; /* just for cleanliness */
  	state->evalfunc = ExecJustScanVar;
  
  	/* See comments in ExecJustInnerVarFirst */
--- 1840,1852 ----
  static Datum
  ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_var);
! 	int			attnum = sop->attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_scantuple;
  
! 	CheckVarSlotCompatibility(slot, attnum, sop->vartype);
! 	sop->opcode = EEOP_SCAN_VAR; /* just for cleanliness */
  	state->evalfunc = ExecJustScanVar;
  
  	/* See comments in ExecJustInnerVarFirst */
***************
*** 1713,1720 **** ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.var.attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_scantuple;
  
  	/* See comments in ExecJustInnerVarFirst */
--- 1857,1865 ----
  static Datum
  ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_var);
! 	int			attnum = sop->attnum + 1;
  	TupleTableSlot *slot = econtext->ecxt_scantuple;
  
  	/* See comments in ExecJustInnerVarFirst */
***************
*** 1725,1743 **** ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[0];
  
! 	*isnull = op->d.constval.isnull;
! 	return op->d.constval.value;
  }
  
  /* Evaluate inner Var and assign to appropriate column of result tuple */
  static Datum
  ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.assign_var.attnum + 1;
! 	int			resultnum = op->d.assign_var.resultnum;
  	TupleTableSlot *inslot = econtext->ecxt_innertuple;
  	TupleTableSlot *outslot = state->resultslot;
  
--- 1870,1890 ----
  static Datum
  ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps;
! 	DEF_SOP(ExprEvalStep_constval);
  
! 	*isnull = sop->isnull;
! 	return sop->value;
  }
  
  /* Evaluate inner Var and assign to appropriate column of result tuple */
  static Datum
  ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_assign_var);
! 	int			attnum = sop->attnum + 1;
! 	int			resultnum = sop->resultnum;
  	TupleTableSlot *inslot = econtext->ecxt_innertuple;
  	TupleTableSlot *outslot = state->resultslot;
  
***************
*** 1758,1766 **** ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.assign_var.attnum + 1;
! 	int			resultnum = op->d.assign_var.resultnum;
  	TupleTableSlot *inslot = econtext->ecxt_outertuple;
  	TupleTableSlot *outslot = state->resultslot;
  
--- 1905,1914 ----
  static Datum
  ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_assign_var);
! 	int			attnum = sop->attnum + 1;
! 	int			resultnum = sop->resultnum;
  	TupleTableSlot *inslot = econtext->ecxt_outertuple;
  	TupleTableSlot *outslot = state->resultslot;
  
***************
*** 1774,1782 **** ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
  static Datum
  ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = &state->steps[1];
! 	int			attnum = op->d.assign_var.attnum + 1;
! 	int			resultnum = op->d.assign_var.resultnum;
  	TupleTableSlot *inslot = econtext->ecxt_scantuple;
  	TupleTableSlot *outslot = state->resultslot;
  
--- 1922,1931 ----
  static Datum
  ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
  {
! 	ExprEvalStep *op = state->steps->nextstep;
! 	DEF_SOP(ExprEvalStep_assign_var);
! 	int			attnum = sop->attnum + 1;
! 	int			resultnum = sop->resultnum;
  	TupleTableSlot *inslot = econtext->ecxt_scantuple;
  	TupleTableSlot *outslot = state->resultslot;
  
***************
*** 1844,1854 **** ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
   * ecxt_param_exec_vals array, and can be accessed by array index.
   */
  void
! ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
  	ParamExecData *prm;
  
! 	prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
  	if (unlikely(prm->execPlan != NULL))
  	{
  		/* Parameter not evaluated yet, so go do it */
--- 1993,2004 ----
   * ecxt_param_exec_vals array, and can be accessed by array index.
   */
  void
! ExecEvalParamExec(ExprState *state, ExprEvalStep_param *sop,
!                   ExprContext *econtext)
  {
  	ParamExecData *prm;
  
! 	prm = &(econtext->ecxt_param_exec_vals[sop->paramid]);
  	if (unlikely(prm->execPlan != NULL))
  	{
  		/* Parameter not evaluated yet, so go do it */
***************
*** 1856,1863 **** ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  		/* ExecSetParamPlan should have processed this param... */
  		Assert(prm->execPlan == NULL);
  	}
! 	*op->resvalue = prm->value;
! 	*op->resnull = prm->isnull;
  }
  
  /*
--- 2006,2013 ----
  		/* ExecSetParamPlan should have processed this param... */
  		Assert(prm->execPlan == NULL);
  	}
! 	*sop->resvalue = prm->value;
! 	*sop->resnull = prm->isnull;
  }
  
  /*
***************
*** 1866,1875 **** ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
   * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
   */
  void
! ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
  	ParamListInfo paramInfo = econtext->ecxt_param_list_info;
! 	int			paramId = op->d.param.paramid;
  
  	if (likely(paramInfo &&
  			   paramId > 0 && paramId <= paramInfo->numParams))
--- 2016,2026 ----
   * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
   */
  void
! ExecEvalParamExtern(ExprState *state, ExprEvalStep_param *sop,
!                     ExprContext *econtext)
  {
  	ParamListInfo paramInfo = econtext->ecxt_param_list_info;
! 	int			paramId = sop->paramid;
  
  	if (likely(paramInfo &&
  			   paramId > 0 && paramId <= paramInfo->numParams))
***************
*** 1883,1897 **** ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  		if (likely(OidIsValid(prm->ptype)))
  		{
  			/* safety check in case hook did something unexpected */
! 			if (unlikely(prm->ptype != op->d.param.paramtype))
  				ereport(ERROR,
  						(errcode(ERRCODE_DATATYPE_MISMATCH),
  						 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
  								paramId,
  								format_type_be(prm->ptype),
! 								format_type_be(op->d.param.paramtype))));
! 			*op->resvalue = prm->value;
! 			*op->resnull = prm->isnull;
  			return;
  		}
  	}
--- 2034,2048 ----
  		if (likely(OidIsValid(prm->ptype)))
  		{
  			/* safety check in case hook did something unexpected */
! 			if (unlikely(prm->ptype != sop->paramtype))
  				ereport(ERROR,
  						(errcode(ERRCODE_DATATYPE_MISMATCH),
  						 errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
  								paramId,
  								format_type_be(prm->ptype),
! 								format_type_be(sop->paramtype))));
! 			*sop->resvalue = prm->value;
! 			*sop->resnull = prm->isnull;
  			return;
  		}
  	}
***************
*** 1905,1916 **** ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
   * Evaluate a SQLValueFunction expression.
   */
  void
! ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
  {
! 	SQLValueFunction *svf = op->d.sqlvaluefunction.svf;
  	FunctionCallInfoData fcinfo;
  
! 	*op->resnull = false;
  
  	/*
  	 * Note: current_schema() can return NULL.  current_user() etc currently
--- 2056,2067 ----
   * Evaluate a SQLValueFunction expression.
   */
  void
! ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep_sqlvaluefunction *sop)
  {
! 	SQLValueFunction *svf = sop->svf;
  	FunctionCallInfoData fcinfo;
  
! 	*sop->resnull = false;
  
  	/*
  	 * Note: current_schema() can return NULL.  current_user() etc currently
***************
*** 1919,1963 **** ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
  	switch (svf->op)
  	{
  		case SVFOP_CURRENT_DATE:
! 			*op->resvalue = DateADTGetDatum(GetSQLCurrentDate());
  			break;
  		case SVFOP_CURRENT_TIME:
  		case SVFOP_CURRENT_TIME_N:
! 			*op->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
  			break;
  		case SVFOP_CURRENT_TIMESTAMP:
  		case SVFOP_CURRENT_TIMESTAMP_N:
! 			*op->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
  			break;
  		case SVFOP_LOCALTIME:
  		case SVFOP_LOCALTIME_N:
! 			*op->resvalue = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
  			break;
  		case SVFOP_LOCALTIMESTAMP:
  		case SVFOP_LOCALTIMESTAMP_N:
! 			*op->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
  			break;
  		case SVFOP_CURRENT_ROLE:
  		case SVFOP_CURRENT_USER:
  		case SVFOP_USER:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*op->resvalue = current_user(&fcinfo);
! 			*op->resnull = fcinfo.isnull;
  			break;
  		case SVFOP_SESSION_USER:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*op->resvalue = session_user(&fcinfo);
! 			*op->resnull = fcinfo.isnull;
  			break;
  		case SVFOP_CURRENT_CATALOG:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*op->resvalue = current_database(&fcinfo);
! 			*op->resnull = fcinfo.isnull;
  			break;
  		case SVFOP_CURRENT_SCHEMA:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*op->resvalue = current_schema(&fcinfo);
! 			*op->resnull = fcinfo.isnull;
  			break;
  	}
  }
--- 2070,2114 ----
  	switch (svf->op)
  	{
  		case SVFOP_CURRENT_DATE:
! 			*sop->resvalue = DateADTGetDatum(GetSQLCurrentDate());
  			break;
  		case SVFOP_CURRENT_TIME:
  		case SVFOP_CURRENT_TIME_N:
! 			*sop->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
  			break;
  		case SVFOP_CURRENT_TIMESTAMP:
  		case SVFOP_CURRENT_TIMESTAMP_N:
! 			*sop->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
  			break;
  		case SVFOP_LOCALTIME:
  		case SVFOP_LOCALTIME_N:
! 			*sop->resvalue = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
  			break;
  		case SVFOP_LOCALTIMESTAMP:
  		case SVFOP_LOCALTIMESTAMP_N:
! 			*sop->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
  			break;
  		case SVFOP_CURRENT_ROLE:
  		case SVFOP_CURRENT_USER:
  		case SVFOP_USER:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*sop->resvalue = current_user(&fcinfo);
! 			*sop->resnull = fcinfo.isnull;
  			break;
  		case SVFOP_SESSION_USER:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*sop->resvalue = session_user(&fcinfo);
! 			*sop->resnull = fcinfo.isnull;
  			break;
  		case SVFOP_CURRENT_CATALOG:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*sop->resvalue = current_database(&fcinfo);
! 			*sop->resnull = fcinfo.isnull;
  			break;
  		case SVFOP_CURRENT_SCHEMA:
  			InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
! 			*sop->resvalue = current_schema(&fcinfo);
! 			*sop->resnull = fcinfo.isnull;
  			break;
  	}
  }
***************
*** 1972,1978 **** ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
   * table whose FDW doesn't handle it, and complain accordingly.
   */
  void
! ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op)
  {
  	ereport(ERROR,
  			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
--- 2123,2129 ----
   * table whose FDW doesn't handle it, and complain accordingly.
   */
  void
! ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *sop)
  {
  	ereport(ERROR,
  			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
***************
*** 1983,2035 **** ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op)
   * Evaluate NextValueExpr.
   */
  void
! ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op)
  {
! 	int64		newval = nextval_internal(op->d.nextvalueexpr.seqid, false);
  
! 	switch (op->d.nextvalueexpr.seqtypid)
  	{
  		case INT2OID:
! 			*op->resvalue = Int16GetDatum((int16) newval);
  			break;
  		case INT4OID:
! 			*op->resvalue = Int32GetDatum((int32) newval);
  			break;
  		case INT8OID:
! 			*op->resvalue = Int64GetDatum((int64) newval);
  			break;
  		default:
  			elog(ERROR, "unsupported sequence type %u",
! 				 op->d.nextvalueexpr.seqtypid);
  	}
! 	*op->resnull = false;
  }
  
  /*
   * Evaluate NullTest / IS NULL for rows.
   */
  void
! ExecEvalRowNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	ExecEvalRowNullInt(state, op, econtext, true);
  }
  
  /*
   * Evaluate NullTest / IS NOT NULL for rows.
   */
  void
! ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	ExecEvalRowNullInt(state, op, econtext, false);
  }
  
  /* Common code for IS [NOT] NULL on a row value */
  static void
! ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
  				   ExprContext *econtext, bool checkisnull)
  {
! 	Datum		value = *op->resvalue;
! 	bool		isnull = *op->resnull;
  	HeapTupleHeader tuple;
  	Oid			tupType;
  	int32		tupTypmod;
--- 2134,2188 ----
   * Evaluate NextValueExpr.
   */
  void
! ExecEvalNextValueExpr(ExprState *state, ExprEvalStep_nextvalueexpr *sop)
  {
! 	int64		newval = nextval_internal(sop->seqid, false);
  
! 	switch (sop->seqtypid)
  	{
  		case INT2OID:
! 			*sop->resvalue = Int16GetDatum((int16) newval);
  			break;
  		case INT4OID:
! 			*sop->resvalue = Int32GetDatum((int32) newval);
  			break;
  		case INT8OID:
! 			*sop->resvalue = Int64GetDatum((int64) newval);
  			break;
  		default:
  			elog(ERROR, "unsupported sequence type %u",
! 				 sop->seqtypid);
  	}
! 	*sop->resnull = false;
  }
  
  /*
   * Evaluate NullTest / IS NULL for rows.
   */
  void
! ExecEvalRowNull(ExprState *state, ExprEvalStep_nulltest_row *sop,
!                 ExprContext *econtext)
  {
! 	ExecEvalRowNullInt(state, sop, econtext, true);
  }
  
  /*
   * Evaluate NullTest / IS NOT NULL for rows.
   */
  void
! ExecEvalRowNotNull(ExprState *state, ExprEvalStep_nulltest_row *sop,
!                    ExprContext *econtext)
  {
! 	ExecEvalRowNullInt(state, sop, econtext, false);
  }
  
  /* Common code for IS [NOT] NULL on a row value */
  static void
! ExecEvalRowNullInt(ExprState *state, ExprEvalStep_nulltest_row *sop,
  				   ExprContext *econtext, bool checkisnull)
  {
! 	Datum		value = *sop->resvalue;
! 	bool		isnull = *sop->resnull;
  	HeapTupleHeader tuple;
  	Oid			tupType;
  	int32		tupTypmod;
***************
*** 2037,2048 **** ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
  	HeapTupleData tmptup;
  	int			att;
  
! 	*op->resnull = false;
  
  	/* NULL row variables are treated just as NULL scalar columns */
  	if (isnull)
  	{
! 		*op->resvalue = BoolGetDatum(checkisnull);
  		return;
  	}
  
--- 2190,2201 ----
  	HeapTupleData tmptup;
  	int			att;
  
! 	*sop->resnull = false;
  
  	/* NULL row variables are treated just as NULL scalar columns */
  	if (isnull)
  	{
! 		*sop->resvalue = BoolGetDatum(checkisnull);
  		return;
  	}
  
***************
*** 2069,2075 **** ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
  
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = get_cached_rowtype(tupType, tupTypmod,
! 								 &op->d.nulltest_row.argdesc,
  								 econtext);
  
  	/*
--- 2222,2228 ----
  
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = get_cached_rowtype(tupType, tupTypmod,
! 								 &sop->argdesc,
  								 econtext);
  
  	/*
***************
*** 2088,2094 **** ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
  			/* null field disproves IS NOT NULL */
  			if (!checkisnull)
  			{
! 				*op->resvalue = BoolGetDatum(false);
  				return;
  			}
  		}
--- 2241,2247 ----
  			/* null field disproves IS NOT NULL */
  			if (!checkisnull)
  			{
! 				*sop->resvalue = BoolGetDatum(false);
  				return;
  			}
  		}
***************
*** 2097,2140 **** ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
  			/* non-null field disproves IS NULL */
  			if (checkisnull)
  			{
! 				*op->resvalue = BoolGetDatum(false);
  				return;
  			}
  		}
  	}
  
! 	*op->resvalue = BoolGetDatum(true);
  }
  
  /*
   * Evaluate an ARRAY[] expression.
   *
   * The individual array elements (or subarrays) have already been evaluated
!  * into op->d.arrayexpr.elemvalues[]/elemnulls[].
   */
  void
! ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
  {
  	ArrayType  *result;
! 	Oid			element_type = op->d.arrayexpr.elemtype;
! 	int			nelems = op->d.arrayexpr.nelems;
  	int			ndims = 0;
  	int			dims[MAXDIM];
  	int			lbs[MAXDIM];
  
  	/* Set non-null as default */
! 	*op->resnull = false;
  
! 	if (!op->d.arrayexpr.multidims)
  	{
  		/* Elements are presumably of scalar type */
! 		Datum	   *dvalues = op->d.arrayexpr.elemvalues;
! 		bool	   *dnulls = op->d.arrayexpr.elemnulls;
  
  		/* Shouldn't happen here, but if length is 0, return empty array */
  		if (nelems == 0)
  		{
! 			*op->resvalue =
  				PointerGetDatum(construct_empty_array(element_type));
  			return;
  		}
--- 2250,2293 ----
  			/* non-null field disproves IS NULL */
  			if (checkisnull)
  			{
! 				*sop->resvalue = BoolGetDatum(false);
  				return;
  			}
  		}
  	}
  
! 	*sop->resvalue = BoolGetDatum(true);
  }
  
  /*
   * Evaluate an ARRAY[] expression.
   *
   * The individual array elements (or subarrays) have already been evaluated
!  * into sop->elemvalues[]/elemnulls[].
   */
  void
! ExecEvalArrayExpr(ExprState *state, ExprEvalStep_arrayexpr *sop)
  {
  	ArrayType  *result;
! 	Oid			element_type = sop->elemtype;
! 	int			nelems = sop->nelems;
  	int			ndims = 0;
  	int			dims[MAXDIM];
  	int			lbs[MAXDIM];
  
  	/* Set non-null as default */
! 	*sop->resnull = false;
  
! 	if (!sop->multidims)
  	{
  		/* Elements are presumably of scalar type */
! 		Datum	   *dvalues = sop->elemvalues;
! 		bool	   *dnulls = sop->elemnulls;
  
  		/* Shouldn't happen here, but if length is 0, return empty array */
  		if (nelems == 0)
  		{
! 			*sop->resvalue =
  				PointerGetDatum(construct_empty_array(element_type));
  			return;
  		}
***************
*** 2146,2154 **** ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
  
  		result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
  									element_type,
! 									op->d.arrayexpr.elemlength,
! 									op->d.arrayexpr.elembyval,
! 									op->d.arrayexpr.elemalign);
  	}
  	else
  	{
--- 2299,2307 ----
  
  		result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
  									element_type,
! 									sop->elemlength,
! 									sop->elembyval,
! 									sop->elemalign);
  	}
  	else
  	{
***************
*** 2185,2192 **** ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
  			ArrayType  *array;
  			int			this_ndims;
  
! 			arraydatum = op->d.arrayexpr.elemvalues[elemoff];
! 			eisnull = op->d.arrayexpr.elemnulls[elemoff];
  
  			/* temporarily ignore null subarrays */
  			if (eisnull)
--- 2338,2345 ----
  			ArrayType  *array;
  			int			this_ndims;
  
! 			arraydatum = sop->elemvalues[elemoff];
! 			eisnull = sop->elemnulls[elemoff];
  
  			/* temporarily ignore null subarrays */
  			if (eisnull)
***************
*** 2268,2274 **** ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
  		{
  			if (ndims == 0)		/* didn't find any nonempty array */
  			{
! 				*op->resvalue = PointerGetDatum(construct_empty_array(element_type));
  				return;
  			}
  			ereport(ERROR,
--- 2421,2427 ----
  		{
  			if (ndims == 0)		/* didn't find any nonempty array */
  			{
! 				*sop->resvalue = PointerGetDatum(construct_empty_array(element_type));
  				return;
  			}
  			ereport(ERROR,
***************
*** 2319,2325 **** ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
  		}
  	}
  
! 	*op->resvalue = PointerGetDatum(result);
  }
  
  /*
--- 2472,2478 ----
  		}
  	}
  
! 	*sop->resvalue = PointerGetDatum(result);
  }
  
  /*
***************
*** 2328,2344 **** ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
   * Source array is in step's result variable.
   */
  void
! ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
  {
! 	ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr;
  	Datum		arraydatum;
  	FunctionCallInfoData locfcinfo;
  
  	/* NULL array -> NULL result */
! 	if (*op->resnull)
  		return;
  
! 	arraydatum = *op->resvalue;
  
  	/*
  	 * If it's binary-compatible, modify the element type in the array header,
--- 2481,2497 ----
   * Source array is in step's result variable.
   */
  void
! ExecEvalArrayCoerce(ExprState *state, ExprEvalStep_arraycoerce *sop)
  {
! 	ArrayCoerceExpr *acoerce = sop->coerceexpr;
  	Datum		arraydatum;
  	FunctionCallInfoData locfcinfo;
  
  	/* NULL array -> NULL result */
! 	if (*sop->resnull)
  		return;
  
! 	arraydatum = *sop->resvalue;
  
  	/*
  	 * If it's binary-compatible, modify the element type in the array header,
***************
*** 2349,2356 **** ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
  		/* Detoast input array if necessary, and copy in any case */
  		ArrayType  *array = DatumGetArrayTypePCopy(arraydatum);
  
! 		ARR_ELEMTYPE(array) = op->d.arraycoerce.resultelemtype;
! 		*op->resvalue = PointerGetDatum(array);
  		return;
  	}
  
--- 2502,2509 ----
  		/* Detoast input array if necessary, and copy in any case */
  		ArrayType  *array = DatumGetArrayTypePCopy(arraydatum);
  
! 		ARR_ELEMTYPE(array) = sop->resultelemtype;
! 		*sop->resvalue = PointerGetDatum(array);
  		return;
  	}
  
***************
*** 2362,2368 **** ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
  	 *
  	 * Note: coercion functions are assumed to not use collation.
  	 */
! 	InitFunctionCallInfoData(locfcinfo, op->d.arraycoerce.elemfunc, 3,
  							 InvalidOid, NULL, NULL);
  	locfcinfo.arg[0] = arraydatum;
  	locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
--- 2515,2521 ----
  	 *
  	 * Note: coercion functions are assumed to not use collation.
  	 */
! 	InitFunctionCallInfoData(locfcinfo, sop->elemfunc, 3,
  							 InvalidOid, NULL, NULL);
  	locfcinfo.arg[0] = arraydatum;
  	locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
***************
*** 2371,2413 **** ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
  	locfcinfo.argnull[1] = false;
  	locfcinfo.argnull[2] = false;
  
! 	*op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype,
! 							  op->d.arraycoerce.amstate);
  }
  
  /*
   * Evaluate a ROW() expression.
   *
   * The individual columns have already been evaluated into
!  * op->d.row.elemvalues[]/elemnulls[].
   */
  void
! ExecEvalRow(ExprState *state, ExprEvalStep *op)
  {
  	HeapTuple	tuple;
  
  	/* build tuple from evaluated field values */
! 	tuple = heap_form_tuple(op->d.row.tupdesc,
! 							op->d.row.elemvalues,
! 							op->d.row.elemnulls);
  
! 	*op->resvalue = HeapTupleGetDatum(tuple);
! 	*op->resnull = false;
  }
  
  /*
   * Evaluate GREATEST() or LEAST() expression (note this is *not* MIN()/MAX()).
   *
   * All of the to-be-compared expressions have already been evaluated into
!  * op->d.minmax.values[]/nulls[].
   */
  void
! ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
  {
! 	Datum	   *values = op->d.minmax.values;
! 	bool	   *nulls = op->d.minmax.nulls;
! 	FunctionCallInfo fcinfo = op->d.minmax.fcinfo_data;
! 	MinMaxOp	operator = op->d.minmax.op;
  	int			off;
  
  	/* set at initialization */
--- 2524,2565 ----
  	locfcinfo.argnull[1] = false;
  	locfcinfo.argnull[2] = false;
  
! 	*sop->resvalue = array_map(&locfcinfo, sop->resultelemtype, sop->amstate);
  }
  
  /*
   * Evaluate a ROW() expression.
   *
   * The individual columns have already been evaluated into
!  * sop->elemvalues[]/elemnulls[].
   */
  void
! ExecEvalRow(ExprState *state, ExprEvalStep_row *sop)
  {
  	HeapTuple	tuple;
  
  	/* build tuple from evaluated field values */
! 	tuple = heap_form_tuple(sop->tupdesc,
! 							sop->elemvalues,
! 							sop->elemnulls);
  
! 	*sop->resvalue = HeapTupleGetDatum(tuple);
! 	*sop->resnull = false;
  }
  
  /*
   * Evaluate GREATEST() or LEAST() expression (note this is *not* MIN()/MAX()).
   *
   * All of the to-be-compared expressions have already been evaluated into
!  * sop->values[]/nulls[].
   */
  void
! ExecEvalMinMax(ExprState *state, ExprEvalStep_minmax *sop)
  {
! 	Datum	   *values = sop->values;
! 	bool	   *nulls = sop->nulls;
! 	FunctionCallInfo fcinfo = sop->fcinfo_data;
! 	MinMaxOp	operator = sop->op;
  	int			off;
  
  	/* set at initialization */
***************
*** 2415,2440 **** ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
  	Assert(fcinfo->argnull[1] == false);
  
  	/* default to null result */
! 	*op->resnull = true;
  
! 	for (off = 0; off < op->d.minmax.nelems; off++)
  	{
  		/* ignore NULL inputs */
  		if (nulls[off])
  			continue;
  
! 		if (*op->resnull)
  		{
  			/* first nonnull input, adopt value */
! 			*op->resvalue = values[off];
! 			*op->resnull = false;
  		}
  		else
  		{
  			int			cmpresult;
  
  			/* apply comparison function */
! 			fcinfo->arg[0] = *op->resvalue;
  			fcinfo->arg[1] = values[off];
  
  			fcinfo->isnull = false;
--- 2567,2592 ----
  	Assert(fcinfo->argnull[1] == false);
  
  	/* default to null result */
! 	*sop->resnull = true;
  
! 	for (off = 0; off < sop->nelems; off++)
  	{
  		/* ignore NULL inputs */
  		if (nulls[off])
  			continue;
  
! 		if (*sop->resnull)
  		{
  			/* first nonnull input, adopt value */
! 			*sop->resvalue = values[off];
! 			*sop->resnull = false;
  		}
  		else
  		{
  			int			cmpresult;
  
  			/* apply comparison function */
! 			fcinfo->arg[0] = *sop->resvalue;
  			fcinfo->arg[1] = values[off];
  
  			fcinfo->isnull = false;
***************
*** 2443,2451 **** ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
  				continue;
  
  			if (cmpresult > 0 && operator == IS_LEAST)
! 				*op->resvalue = values[off];
  			else if (cmpresult < 0 && operator == IS_GREATEST)
! 				*op->resvalue = values[off];
  		}
  	}
  }
--- 2595,2603 ----
  				continue;
  
  			if (cmpresult > 0 && operator == IS_LEAST)
! 				*sop->resvalue = values[off];
  			else if (cmpresult < 0 && operator == IS_GREATEST)
! 				*sop->resvalue = values[off];
  		}
  	}
  }
***************
*** 2456,2464 **** ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
   * Source record is in step's result variable.
   */
  void
! ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	AttrNumber	fieldnum = op->d.fieldselect.fieldnum;
  	Datum		tupDatum;
  	HeapTupleHeader tuple;
  	Oid			tupType;
--- 2608,2617 ----
   * Source record is in step's result variable.
   */
  void
! ExecEvalFieldSelect(ExprState *state, ExprEvalStep_fieldselect *sop,
!                     ExprContext *econtext)
  {
! 	AttrNumber	fieldnum = sop->fieldnum;
  	Datum		tupDatum;
  	HeapTupleHeader tuple;
  	Oid			tupType;
***************
*** 2468,2478 **** ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  	HeapTupleData tmptup;
  
  	/* NULL record -> NULL result */
! 	if (*op->resnull)
  		return;
  
  	/* Get the composite datum and extract its type fields */
! 	tupDatum = *op->resvalue;
  	tuple = DatumGetHeapTupleHeader(tupDatum);
  
  	tupType = HeapTupleHeaderGetTypeId(tuple);
--- 2621,2631 ----
  	HeapTupleData tmptup;
  
  	/* NULL record -> NULL result */
! 	if (*sop->resnull)
  		return;
  
  	/* Get the composite datum and extract its type fields */
! 	tupDatum = *sop->resvalue;
  	tuple = DatumGetHeapTupleHeader(tupDatum);
  
  	tupType = HeapTupleHeaderGetTypeId(tuple);
***************
*** 2480,2486 **** ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = get_cached_rowtype(tupType, tupTypmod,
! 								 &op->d.fieldselect.argdesc,
  								 econtext);
  
  	/*
--- 2633,2639 ----
  
  	/* Lookup tupdesc if first time through or if type changes */
  	tupDesc = get_cached_rowtype(tupType, tupTypmod,
! 								 &sop->argdesc,
  								 econtext);
  
  	/*
***************
*** 2499,2527 **** ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  	/* Check for dropped column, and force a NULL result if so */
  	if (attr->attisdropped)
  	{
! 		*op->resnull = true;
  		return;
  	}
  
  	/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
  	/* As in CheckVarSlotCompatibility, we should but can't check typmod */
! 	if (op->d.fieldselect.resulttype != attr->atttypid)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
  				 errmsg("attribute %d has wrong type", fieldnum),
  				 errdetail("Table has type %s, but query expects %s.",
  						   format_type_be(attr->atttypid),
! 						   format_type_be(op->d.fieldselect.resulttype))));
  
  	/* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
  	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
  	tmptup.t_data = tuple;
  
  	/* extract the field */
! 	*op->resvalue = heap_getattr(&tmptup,
  								 fieldnum,
  								 tupDesc,
! 								 op->resnull);
  }
  
  /*
--- 2652,2680 ----
  	/* Check for dropped column, and force a NULL result if so */
  	if (attr->attisdropped)
  	{
! 		*sop->resnull = true;
  		return;
  	}
  
  	/* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
  	/* As in CheckVarSlotCompatibility, we should but can't check typmod */
! 	if (sop->resulttype != attr->atttypid)
  		ereport(ERROR,
  				(errcode(ERRCODE_DATATYPE_MISMATCH),
  				 errmsg("attribute %d has wrong type", fieldnum),
  				 errdetail("Table has type %s, but query expects %s.",
  						   format_type_be(attr->atttypid),
! 						   format_type_be(sop->resulttype))));
  
  	/* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
  	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
  	tmptup.t_data = tuple;
  
  	/* extract the field */
! 	*sop->resvalue = heap_getattr(&tmptup,
  								 fieldnum,
  								 tupDesc,
! 								 sop->resnull);
  }
  
  /*
***************
*** 2534,2557 **** ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
   * Source record is in step's result variable.
   */
  void
! ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
  	TupleDesc	tupDesc;
  
  	/* Lookup tupdesc if first time through or after rescan */
! 	tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
! 								 op->d.fieldstore.argdesc, econtext);
  
  	/* Check that current tupdesc doesn't have more fields than we allocated */
! 	if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
  		elog(ERROR, "too many columns in composite type %u",
! 			 op->d.fieldstore.fstore->resulttype);
  
! 	if (*op->resnull)
  	{
  		/* Convert null input tuple into an all-nulls row */
! 		memset(op->d.fieldstore.nulls, true,
! 			   op->d.fieldstore.ncolumns * sizeof(bool));
  	}
  	else
  	{
--- 2687,2711 ----
   * Source record is in step's result variable.
   */
  void
! ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep_fieldstore *sop,
!                          ExprContext *econtext)
  {
  	TupleDesc	tupDesc;
  
  	/* Lookup tupdesc if first time through or after rescan */
! 	tupDesc = get_cached_rowtype(sop->fstore->resulttype, -1,
! 								 sop->argdesc, econtext);
  
  	/* Check that current tupdesc doesn't have more fields than we allocated */
! 	if (unlikely(tupDesc->natts > sop->ncolumns))
  		elog(ERROR, "too many columns in composite type %u",
! 			 sop->fstore->resulttype);
  
! 	if (*sop->resnull)
  	{
  		/* Convert null input tuple into an all-nulls row */
! 		memset(sop->nulls, true,
! 			   sop->ncolumns * sizeof(bool));
  	}
  	else
  	{
***************
*** 2559,2565 **** ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
  		 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
  		 * set all the fields in the struct just in case.
  		 */
! 		Datum		tupDatum = *op->resvalue;
  		HeapTupleHeader tuphdr;
  		HeapTupleData tmptup;
  
--- 2713,2719 ----
  		 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
  		 * set all the fields in the struct just in case.
  		 */
! 		Datum		tupDatum = *sop->resvalue;
  		HeapTupleHeader tuphdr;
  		HeapTupleData tmptup;
  
***************
*** 2570,2577 **** ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
  		tmptup.t_data = tuphdr;
  
  		heap_deform_tuple(&tmptup, tupDesc,
! 						  op->d.fieldstore.values,
! 						  op->d.fieldstore.nulls);
  	}
  }
  
--- 2724,2731 ----
  		tmptup.t_data = tuphdr;
  
  		heap_deform_tuple(&tmptup, tupDesc,
! 						  sop->values,
! 						  sop->nulls);
  	}
  }
  
***************
*** 2580,2596 **** ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econte
   * FieldStore expression has been evaluated.
   */
  void
! ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
  	HeapTuple	tuple;
  
  	/* argdesc should already be valid from the DeForm step */
! 	tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
! 							op->d.fieldstore.values,
! 							op->d.fieldstore.nulls);
  
! 	*op->resvalue = HeapTupleGetDatum(tuple);
! 	*op->resnull = false;
  }
  
  /*
--- 2734,2751 ----
   * FieldStore expression has been evaluated.
   */
  void
! ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep_fieldstore *sop,
!                        ExprContext *econtext)
  {
  	HeapTuple	tuple;
  
  	/* argdesc should already be valid from the DeForm step */
! 	tuple = heap_form_tuple(*sop->argdesc,
! 							sop->values,
! 							sop->nulls);
  
! 	*sop->resvalue = HeapTupleGetDatum(tuple);
! 	*sop->resnull = false;
  }
  
  /*
***************
*** 2605,2613 **** ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
   * lowerindex[] for use later.
   */
  bool
! ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op)
  {
! 	ArrayRefState *arefstate = op->d.arrayref_subscript.state;
  	int		   *indexes;
  	int			off;
  
--- 2760,2769 ----
   * lowerindex[] for use later.
   */
  bool
! ExecEvalArrayRefSubscript(ExprState *state,
!                           ExprEvalStep_arrayref_subscript *sop)
  {
! 	ArrayRefState *arefstate = sop->state;
  	int		   *indexes;
  	int			off;
  
***************
*** 2617,2633 **** ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op)
  		if (arefstate->isassignment)
  			ereport(ERROR,
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
! 					 errmsg("array subscript in assignment must not be null")));
! 		*op->resnull = true;
  		return false;
  	}
  
  	/* Convert datum to int, save in appropriate place */
! 	if (op->d.arrayref_subscript.isupper)
  		indexes = arefstate->upperindex;
  	else
  		indexes = arefstate->lowerindex;
! 	off = op->d.arrayref_subscript.off;
  
  	indexes[off] = DatumGetInt32(arefstate->subscriptvalue);
  
--- 2773,2789 ----
  		if (arefstate->isassignment)
  			ereport(ERROR,
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
! 				    errmsg("array subscript in assignment must not be null")));
! 		*sop->resnull = true;
  		return false;
  	}
  
  	/* Convert datum to int, save in appropriate place */
! 	if (sop->isupper)
  		indexes = arefstate->upperindex;
  	else
  		indexes = arefstate->lowerindex;
! 	off = sop->off;
  
  	indexes[off] = DatumGetInt32(arefstate->subscriptvalue);
  
***************
*** 2640,2677 **** ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op)
   * Source array is in step's result variable.
   */
  void
! ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op)
  {
! 	ArrayRefState *arefstate = op->d.arrayref.state;
  
  	/* Should not get here if source array (or any subscript) is null */
! 	Assert(!(*op->resnull));
  
  	if (arefstate->numlower == 0)
  	{
  		/* Scalar case */
! 		*op->resvalue = array_get_element(*op->resvalue,
! 										  arefstate->numupper,
! 										  arefstate->upperindex,
! 										  arefstate->refattrlength,
! 										  arefstate->refelemlength,
! 										  arefstate->refelembyval,
! 										  arefstate->refelemalign,
! 										  op->resnull);
  	}
  	else
  	{
  		/* Slice case */
! 		*op->resvalue = array_get_slice(*op->resvalue,
! 										arefstate->numupper,
! 										arefstate->upperindex,
! 										arefstate->lowerindex,
! 										arefstate->upperprovided,
! 										arefstate->lowerprovided,
! 										arefstate->refattrlength,
! 										arefstate->refelemlength,
! 										arefstate->refelembyval,
! 										arefstate->refelemalign);
  	}
  }
  
--- 2796,2833 ----
   * Source array is in step's result variable.
   */
  void
! ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep_arrayref *sop)
  {
! 	ArrayRefState *arefstate = sop->state;
  
  	/* Should not get here if source array (or any subscript) is null */
! 	Assert(!(*sop->resnull));
  
  	if (arefstate->numlower == 0)
  	{
  		/* Scalar case */
! 		*sop->resvalue = array_get_element(*sop->resvalue,
! 										   arefstate->numupper,
! 										   arefstate->upperindex,
! 										   arefstate->refattrlength,
! 										   arefstate->refelemlength,
! 										   arefstate->refelembyval,
! 										   arefstate->refelemalign,
! 										   sop->resnull);
  	}
  	else
  	{
  		/* Slice case */
! 		*sop->resvalue = array_get_slice(*sop->resvalue,
! 										 arefstate->numupper,
! 										 arefstate->upperindex,
! 										 arefstate->lowerindex,
! 										 arefstate->upperprovided,
! 										 arefstate->lowerprovided,
! 										 arefstate->refattrlength,
! 										 arefstate->refelemlength,
! 										 arefstate->refelembyval,
! 										 arefstate->refelemalign);
  	}
  }
  
***************
*** 2682,2692 **** ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op)
   * ArrayRefState's prevvalue/prevnull fields.
   */
  void
! ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
  {
! 	ArrayRefState *arefstate = op->d.arrayref.state;
  
! 	if (*op->resnull)
  	{
  		/* whole array is null, so any element or slice is too */
  		arefstate->prevvalue = (Datum) 0;
--- 2838,2848 ----
   * ArrayRefState's prevvalue/prevnull fields.
   */
  void
! ExecEvalArrayRefOld(ExprState *state, ExprEvalStep_arrayref *sop)
  {
! 	ArrayRefState *arefstate = sop->state;
  
! 	if (*sop->resnull)
  	{
  		/* whole array is null, so any element or slice is too */
  		arefstate->prevvalue = (Datum) 0;
***************
*** 2695,2701 **** ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
  	else if (arefstate->numlower == 0)
  	{
  		/* Scalar case */
! 		arefstate->prevvalue = array_get_element(*op->resvalue,
  												 arefstate->numupper,
  												 arefstate->upperindex,
  												 arefstate->refattrlength,
--- 2851,2857 ----
  	else if (arefstate->numlower == 0)
  	{
  		/* Scalar case */
! 		arefstate->prevvalue = array_get_element(*sop->resvalue,
  												 arefstate->numupper,
  												 arefstate->upperindex,
  												 arefstate->refattrlength,
***************
*** 2708,2714 **** ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
  	{
  		/* Slice case */
  		/* this is currently unreachable */
! 		arefstate->prevvalue = array_get_slice(*op->resvalue,
  											   arefstate->numupper,
  											   arefstate->upperindex,
  											   arefstate->lowerindex,
--- 2864,2870 ----
  	{
  		/* Slice case */
  		/* this is currently unreachable */
! 		arefstate->prevvalue = array_get_slice(*sop->resvalue,
  											   arefstate->numupper,
  											   arefstate->upperindex,
  											   arefstate->lowerindex,
***************
*** 2729,2737 **** ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
   * ArrayRefState's replacevalue/replacenull.
   */
  void
! ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op)
  {
! 	ArrayRefState *arefstate = op->d.arrayref.state;
  
  	/*
  	 * For an assignment to a fixed-length array type, both the original array
--- 2885,2893 ----
   * ArrayRefState's replacevalue/replacenull.
   */
  void
! ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep_arrayref *sop)
  {
! 	ArrayRefState *arefstate = sop->state;
  
  	/*
  	 * For an assignment to a fixed-length array type, both the original array
***************
*** 2740,2746 **** ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op)
  	 */
  	if (arefstate->refattrlength > 0)	/* fixed-length array? */
  	{
! 		if (*op->resnull || arefstate->replacenull)
  			return;
  	}
  
--- 2896,2902 ----
  	 */
  	if (arefstate->refattrlength > 0)	/* fixed-length array? */
  	{
! 		if (*sop->resnull || arefstate->replacenull)
  			return;
  	}
  
***************
*** 2750,2789 **** ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op)
  	 * element will result in a singleton array value.  It does not matter
  	 * whether the new element is NULL.
  	 */
! 	if (*op->resnull)
  	{
! 		*op->resvalue = PointerGetDatum(construct_empty_array(arefstate->refelemtype));
! 		*op->resnull = false;
  	}
  
  	if (arefstate->numlower == 0)
  	{
  		/* Scalar case */
! 		*op->resvalue = array_set_element(*op->resvalue,
! 										  arefstate->numupper,
! 										  arefstate->upperindex,
! 										  arefstate->replacevalue,
! 										  arefstate->replacenull,
! 										  arefstate->refattrlength,
! 										  arefstate->refelemlength,
! 										  arefstate->refelembyval,
! 										  arefstate->refelemalign);
  	}
  	else
  	{
  		/* Slice case */
! 		*op->resvalue = array_set_slice(*op->resvalue,
! 										arefstate->numupper,
! 										arefstate->upperindex,
! 										arefstate->lowerindex,
! 										arefstate->upperprovided,
! 										arefstate->lowerprovided,
! 										arefstate->replacevalue,
! 										arefstate->replacenull,
! 										arefstate->refattrlength,
! 										arefstate->refelemlength,
! 										arefstate->refelembyval,
! 										arefstate->refelemalign);
  	}
  }
  
--- 2906,2945 ----
  	 * element will result in a singleton array value.  It does not matter
  	 * whether the new element is NULL.
  	 */
! 	if (*sop->resnull)
  	{
! 		*sop->resvalue = PointerGetDatum(construct_empty_array(arefstate->refelemtype));
! 		*sop->resnull = false;
  	}
  
  	if (arefstate->numlower == 0)
  	{
  		/* Scalar case */
! 		*sop->resvalue = array_set_element(*sop->resvalue,
! 										   arefstate->numupper,
! 										   arefstate->upperindex,
! 										   arefstate->replacevalue,
! 										   arefstate->replacenull,
! 										   arefstate->refattrlength,
! 										   arefstate->refelemlength,
! 										   arefstate->refelembyval,
! 										   arefstate->refelemalign);
  	}
  	else
  	{
  		/* Slice case */
! 		*sop->resvalue = array_set_slice(*sop->resvalue,
! 										 arefstate->numupper,
! 										 arefstate->upperindex,
! 										 arefstate->lowerindex,
! 										 arefstate->upperprovided,
! 										 arefstate->lowerprovided,
! 										 arefstate->replacevalue,
! 										 arefstate->replacenull,
! 										 arefstate->refattrlength,
! 										 arefstate->refelemlength,
! 										 arefstate->refelembyval,
! 										 arefstate->refelemalign);
  	}
  }
  
***************
*** 2794,2802 **** ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op)
   * Source record is in step's result variable.
   */
  void
! ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	ConvertRowtypeExpr *convert = op->d.convert_rowtype.convert;
  	HeapTuple	result;
  	Datum		tupDatum;
  	HeapTupleHeader tuple;
--- 2950,2959 ----
   * Source record is in step's result variable.
   */
  void
! ExecEvalConvertRowtype(ExprState *state, ExprEvalStep_convert_rowtype *sop,
!                        ExprContext *econtext)
  {
! 	ConvertRowtypeExpr *convert = sop->convert;
  	HeapTuple	result;
  	Datum		tupDatum;
  	HeapTupleHeader tuple;
***************
*** 2805,2834 **** ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
  				outdesc;
  
  	/* NULL in -> NULL out */
! 	if (*op->resnull)
  		return;
  
! 	tupDatum = *op->resvalue;
  	tuple = DatumGetHeapTupleHeader(tupDatum);
  
  	/* Lookup tupdescs if first time through or after rescan */
! 	if (op->d.convert_rowtype.indesc == NULL)
  	{
  		get_cached_rowtype(exprType((Node *) convert->arg), -1,
! 						   &op->d.convert_rowtype.indesc,
  						   econtext);
! 		op->d.convert_rowtype.initialized = false;
  	}
! 	if (op->d.convert_rowtype.outdesc == NULL)
  	{
  		get_cached_rowtype(convert->resulttype, -1,
! 						   &op->d.convert_rowtype.outdesc,
  						   econtext);
! 		op->d.convert_rowtype.initialized = false;
  	}
  
! 	indesc = op->d.convert_rowtype.indesc;
! 	outdesc = op->d.convert_rowtype.outdesc;
  
  	/*
  	 * We used to be able to assert that incoming tuples are marked with
--- 2962,2991 ----
  				outdesc;
  
  	/* NULL in -> NULL out */
! 	if (*sop->resnull)
  		return;
  
! 	tupDatum = *sop->resvalue;
  	tuple = DatumGetHeapTupleHeader(tupDatum);
  
  	/* Lookup tupdescs if first time through or after rescan */
! 	if (sop->indesc == NULL)
  	{
  		get_cached_rowtype(exprType((Node *) convert->arg), -1,
! 						   &sop->indesc,
  						   econtext);
! 		sop->initialized = false;
  	}
! 	if (sop->outdesc == NULL)
  	{
  		get_cached_rowtype(convert->resulttype, -1,
! 						   &sop->outdesc,
  						   econtext);
! 		sop->initialized = false;
  	}
  
! 	indesc = sop->indesc;
! 	outdesc = sop->outdesc;
  
  	/*
  	 * We used to be able to assert that incoming tuples are marked with
***************
*** 2840,2846 **** ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
  		   HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
  
  	/* if first time through, initialize conversion map */
! 	if (!op->d.convert_rowtype.initialized)
  	{
  		MemoryContext old_cxt;
  
--- 2997,3003 ----
  		   HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
  
  	/* if first time through, initialize conversion map */
! 	if (!sop->initialized)
  	{
  		MemoryContext old_cxt;
  
***************
*** 2848,2857 **** ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
  		old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
  
  		/* prepare map from old to new attribute numbers */
! 		op->d.convert_rowtype.map =
  			convert_tuples_by_name(indesc, outdesc,
  								   gettext_noop("could not convert row type"));
! 		op->d.convert_rowtype.initialized = true;
  
  		MemoryContextSwitchTo(old_cxt);
  	}
--- 3005,3014 ----
  		old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
  
  		/* prepare map from old to new attribute numbers */
! 		sop->map =
  			convert_tuples_by_name(indesc, outdesc,
  								   gettext_noop("could not convert row type"));
! 		sop->initialized = true;
  
  		MemoryContextSwitchTo(old_cxt);
  	}
***************
*** 2860,2871 **** ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
  	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
  	tmptup.t_data = tuple;
  
! 	if (op->d.convert_rowtype.map != NULL)
  	{
  		/* Full conversion with attribute rearrangement needed */
! 		result = do_convert_tuple(&tmptup, op->d.convert_rowtype.map);
  		/* Result already has appropriate composite-datum header fields */
! 		*op->resvalue = HeapTupleGetDatum(result);
  	}
  	else
  	{
--- 3017,3028 ----
  	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
  	tmptup.t_data = tuple;
  
! 	if (sop->map != NULL)
  	{
  		/* Full conversion with attribute rearrangement needed */
! 		result = do_convert_tuple(&tmptup, sop->map);
  		/* Result already has appropriate composite-datum header fields */
! 		*sop->resvalue = HeapTupleGetDatum(result);
  	}
  	else
  	{
***************
*** 2879,2885 **** ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
  		 * datum, so it shouldn't contain any.  So heap_copy_tuple_as_datum()
  		 * is overkill here, but its check for external fields is cheap.
  		 */
! 		*op->resvalue = heap_copy_tuple_as_datum(&tmptup, outdesc);
  	}
  }
  
--- 3036,3042 ----
  		 * datum, so it shouldn't contain any.  So heap_copy_tuple_as_datum()
  		 * is overkill here, but its check for external fields is cheap.
  		 */
! 		*sop->resvalue = heap_copy_tuple_as_datum(&tmptup, outdesc);
  	}
  }
  
***************
*** 2894,2904 **** ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext
   * we short-circuit as soon as the result is known.
   */
  void
! ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  {
! 	FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data;
! 	bool		useOr = op->d.scalararrayop.useOr;
! 	bool		strictfunc = op->d.scalararrayop.finfo->fn_strict;
  	ArrayType  *arr;
  	int			nitems;
  	Datum		result;
--- 3051,3061 ----
   * we short-circuit as soon as the result is known.
   */
  void
! ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep_scalararrayop *sop)
  {
! 	FunctionCallInfo fcinfo = sop->fcinfo_data;
! 	bool		useOr = sop->useOr;
! 	bool		strictfunc = sop->finfo->fn_strict;
  	ArrayType  *arr;
  	int			nitems;
  	Datum		result;
***************
*** 2915,2925 **** ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  	 * If the array is NULL then we return NULL --- it's not very meaningful
  	 * to do anything else, even if the operator isn't strict.
  	 */
! 	if (*op->resnull)
  		return;
  
  	/* Else okay to fetch and detoast the array */
! 	arr = DatumGetArrayTypeP(*op->resvalue);
  
  	/*
  	 * If the array is empty, we return either FALSE or TRUE per the useOr
--- 3072,3082 ----
  	 * If the array is NULL then we return NULL --- it's not very meaningful
  	 * to do anything else, even if the operator isn't strict.
  	 */
! 	if (*sop->resnull)
  		return;
  
  	/* Else okay to fetch and detoast the array */
! 	arr = DatumGetArrayTypeP(*sop->resvalue);
  
  	/*
  	 * If the array is empty, we return either FALSE or TRUE per the useOr
***************
*** 2930,2937 **** ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  	nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
  	if (nitems <= 0)
  	{
! 		*op->resvalue = BoolGetDatum(!useOr);
! 		*op->resnull = false;
  		return;
  	}
  
--- 3087,3094 ----
  	nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
  	if (nitems <= 0)
  	{
! 		*sop->resvalue = BoolGetDatum(!useOr);
! 		*sop->resnull = false;
  		return;
  	}
  
***************
*** 2941,2947 **** ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  	 */
  	if (fcinfo->argnull[0] && strictfunc)
  	{
! 		*op->resnull = true;
  		return;
  	}
  
--- 3098,3104 ----
  	 */
  	if (fcinfo->argnull[0] && strictfunc)
  	{
! 		*sop->resnull = true;
  		return;
  	}
  
***************
*** 2949,2966 **** ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  	 * We arrange to look up info about the element type only once per series
  	 * of calls, assuming the element type doesn't change underneath us.
  	 */
! 	if (op->d.scalararrayop.element_type != ARR_ELEMTYPE(arr))
  	{
  		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
! 							 &op->d.scalararrayop.typlen,
! 							 &op->d.scalararrayop.typbyval,
! 							 &op->d.scalararrayop.typalign);
! 		op->d.scalararrayop.element_type = ARR_ELEMTYPE(arr);
  	}
  
! 	typlen = op->d.scalararrayop.typlen;
! 	typbyval = op->d.scalararrayop.typbyval;
! 	typalign = op->d.scalararrayop.typalign;
  
  	/* Initialize result appropriately depending on useOr */
  	result = BoolGetDatum(!useOr);
--- 3106,3123 ----
  	 * We arrange to look up info about the element type only once per series
  	 * of calls, assuming the element type doesn't change underneath us.
  	 */
! 	if (sop->element_type != ARR_ELEMTYPE(arr))
  	{
  		get_typlenbyvalalign(ARR_ELEMTYPE(arr),
! 							 &sop->typlen,
! 							 &sop->typbyval,
! 							 &sop->typalign);
! 		sop->element_type = ARR_ELEMTYPE(arr);
  	}
  
! 	typlen = sop->typlen;
! 	typbyval = sop->typbyval;
! 	typalign = sop->typalign;
  
  	/* Initialize result appropriately depending on useOr */
  	result = BoolGetDatum(!useOr);
***************
*** 3000,3006 **** ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  		else
  		{
  			fcinfo->isnull = false;
! 			thisresult = (op->d.scalararrayop.fn_addr) (fcinfo);
  		}
  
  		/* Combine results per OR or AND semantics */
--- 3157,3163 ----
  		else
  		{
  			fcinfo->isnull = false;
! 			thisresult = (sop->fn_addr) (fcinfo);
  		}
  
  		/* Combine results per OR or AND semantics */
***************
*** 3037,3075 **** ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
  		}
  	}
  
! 	*op->resvalue = result;
! 	*op->resnull = resultnull;
  }
  
  /*
   * Evaluate a NOT NULL domain constraint.
   */
  void
! ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op)
  {
! 	if (*op->resnull)
  		ereport(ERROR,
  				(errcode(ERRCODE_NOT_NULL_VIOLATION),
  				 errmsg("domain %s does not allow null values",
! 						format_type_be(op->d.domaincheck.resulttype)),
! 				 errdatatype(op->d.domaincheck.resulttype)));
  }
  
  /*
   * Evaluate a CHECK domain constraint.
   */
  void
! ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op)
  {
! 	if (!*op->d.domaincheck.checknull &&
! 		!DatumGetBool(*op->d.domaincheck.checkvalue))
  		ereport(ERROR,
  				(errcode(ERRCODE_CHECK_VIOLATION),
! 				 errmsg("value for domain %s violates check constraint \"%s\"",
! 						format_type_be(op->d.domaincheck.resulttype),
! 						op->d.domaincheck.constraintname),
! 				 errdomainconstraint(op->d.domaincheck.resulttype,
! 									 op->d.domaincheck.constraintname)));
  }
  
  /*
--- 3194,3232 ----
  		}
  	}
  
! 	*sop->resvalue = result;
! 	*sop->resnull = resultnull;
  }
  
  /*
   * Evaluate a NOT NULL domain constraint.
   */
  void
! ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep_domaincheck *sop)
  {
! 	if (*sop->resnull)
  		ereport(ERROR,
  				(errcode(ERRCODE_NOT_NULL_VIOLATION),
  				 errmsg("domain %s does not allow null values",
! 						format_type_be(sop->resulttype)),
! 				 errdatatype(sop->resulttype)));
  }
  
  /*
   * Evaluate a CHECK domain constraint.
   */
  void
! ExecEvalConstraintCheck(ExprState *state, ExprEvalStep_domaincheck *sop)
  {
! 	if (!*sop->checknull &&
! 		!DatumGetBool(*sop->checkvalue))
  		ereport(ERROR,
  				(errcode(ERRCODE_CHECK_VIOLATION),
! 			    errmsg("value for domain %s violates check constraint \"%s\"",
! 					   format_type_be(sop->resulttype),
! 					   sop->constraintname),
! 				       errdomainconstraint(sop->resulttype,
! 									       sop->constraintname)));
  }
  
  /*
***************
*** 3079,3099 **** ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op)
   * and/or argvalue/argnull arrays.
   */
  void
! ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  {
! 	XmlExpr    *xexpr = op->d.xmlexpr.xexpr;
  	Datum		value;
  	int			i;
  
! 	*op->resnull = true;		/* until we get a result */
! 	*op->resvalue = (Datum) 0;
  
  	switch (xexpr->op)
  	{
  		case IS_XMLCONCAT:
  			{
! 				Datum	   *argvalue = op->d.xmlexpr.argvalue;
! 				bool	   *argnull = op->d.xmlexpr.argnull;
  				List	   *values = NIL;
  
  				for (i = 0; i < list_length(xexpr->args); i++)
--- 3236,3256 ----
   * and/or argvalue/argnull arrays.
   */
  void
! ExecEvalXmlExpr(ExprState *state, ExprEvalStep_xmlexpr *sop)
  {
! 	XmlExpr    *xexpr = sop->xexpr;
  	Datum		value;
  	int			i;
  
! 	*sop->resnull = true;		/* until we get a result */
! 	*sop->resvalue = (Datum) 0;
  
  	switch (xexpr->op)
  	{
  		case IS_XMLCONCAT:
  			{
! 				Datum	   *argvalue = sop->argvalue;
! 				bool	   *argnull = sop->argnull;
  				List	   *values = NIL;
  
  				for (i = 0; i < list_length(xexpr->args); i++)
***************
*** 3104,3119 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  
  				if (values != NIL)
  				{
! 					*op->resvalue = PointerGetDatum(xmlconcat(values));
! 					*op->resnull = false;
  				}
  			}
  			break;
  
  		case IS_XMLFOREST:
  			{
! 				Datum	   *argvalue = op->d.xmlexpr.named_argvalue;
! 				bool	   *argnull = op->d.xmlexpr.named_argnull;
  				StringInfoData buf;
  				ListCell   *lc;
  				ListCell   *lc2;
--- 3261,3276 ----
  
  				if (values != NIL)
  				{
! 					*sop->resvalue = PointerGetDatum(xmlconcat(values));
! 					*sop->resnull = false;
  				}
  			}
  			break;
  
  		case IS_XMLFOREST:
  			{
! 				Datum	   *argvalue = sop->named_argvalue;
! 				bool	   *argnull = sop->named_argnull;
  				StringInfoData buf;
  				ListCell   *lc;
  				ListCell   *lc2;
***************
*** 3134,3150 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  										 map_sql_value_to_xml_value(value,
  																	exprType((Node *) e), true),
  										 argname);
! 						*op->resnull = false;
  					}
  					i++;
  				}
  
! 				if (!*op->resnull)
  				{
  					text	   *result;
  
  					result = cstring_to_text_with_len(buf.data, buf.len);
! 					*op->resvalue = PointerGetDatum(result);
  				}
  
  				pfree(buf.data);
--- 3291,3307 ----
  										 map_sql_value_to_xml_value(value,
  																	exprType((Node *) e), true),
  										 argname);
! 						*sop->resnull = false;
  					}
  					i++;
  				}
  
! 				if (!*sop->resnull)
  				{
  					text	   *result;
  
  					result = cstring_to_text_with_len(buf.data, buf.len);
! 					*sop->resvalue = PointerGetDatum(result);
  				}
  
  				pfree(buf.data);
***************
*** 3152,3169 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  			break;
  
  		case IS_XMLELEMENT:
! 			*op->resvalue = PointerGetDatum(xmlelement(xexpr,
! 													   op->d.xmlexpr.named_argvalue,
! 													   op->d.xmlexpr.named_argnull,
! 													   op->d.xmlexpr.argvalue,
! 													   op->d.xmlexpr.argnull));
! 			*op->resnull = false;
  			break;
  
  		case IS_XMLPARSE:
  			{
! 				Datum	   *argvalue = op->d.xmlexpr.argvalue;
! 				bool	   *argnull = op->d.xmlexpr.argnull;
  				text	   *data;
  				bool		preserve_whitespace;
  
--- 3309,3326 ----
  			break;
  
  		case IS_XMLELEMENT:
! 			*sop->resvalue = PointerGetDatum(xmlelement(xexpr,
! 												        sop->named_argvalue,
! 												        sop->named_argnull,
! 													    sop->argvalue,
! 													    sop->argnull));
! 			*sop->resnull = false;
  			break;
  
  		case IS_XMLPARSE:
  			{
! 				Datum	   *argvalue = sop->argvalue;
! 				bool	   *argnull = sop->argnull;
  				text	   *data;
  				bool		preserve_whitespace;
  
***************
*** 3180,3189 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  				value = argvalue[1];
  				preserve_whitespace = DatumGetBool(value);
  
! 				*op->resvalue = PointerGetDatum(xmlparse(data,
! 														 xexpr->xmloption,
! 														 preserve_whitespace));
! 				*op->resnull = false;
  			}
  			break;
  
--- 3337,3346 ----
  				value = argvalue[1];
  				preserve_whitespace = DatumGetBool(value);
  
! 				*sop->resvalue = PointerGetDatum(xmlparse(data,
! 														  xexpr->xmloption,
! 													      preserve_whitespace));
! 				*sop->resnull = false;
  			}
  			break;
  
***************
*** 3197,3207 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  
  				if (xexpr->args)
  				{
! 					isnull = op->d.xmlexpr.argnull[0];
  					if (isnull)
  						arg = NULL;
  					else
! 						arg = DatumGetTextPP(op->d.xmlexpr.argvalue[0]);
  				}
  				else
  				{
--- 3354,3364 ----
  
  				if (xexpr->args)
  				{
! 					isnull = sop->argnull[0];
  					if (isnull)
  						arg = NULL;
  					else
! 						arg = DatumGetTextPP(sop->argvalue[0]);
  				}
  				else
  				{
***************
*** 3209,3225 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  					isnull = false;
  				}
  
! 				*op->resvalue = PointerGetDatum(xmlpi(xexpr->name,
! 													  arg,
! 													  isnull,
! 													  op->resnull));
  			}
  			break;
  
  		case IS_XMLROOT:
  			{
! 				Datum	   *argvalue = op->d.xmlexpr.argvalue;
! 				bool	   *argnull = op->d.xmlexpr.argnull;
  				xmltype    *data;
  				text	   *version;
  				int			standalone;
--- 3366,3382 ----
  					isnull = false;
  				}
  
! 				*sop->resvalue = PointerGetDatum(xmlpi(xexpr->name,
! 													   arg,
! 													   isnull,
! 													   sop->resnull));
  			}
  			break;
  
  		case IS_XMLROOT:
  			{
! 				Datum	   *argvalue = sop->argvalue;
! 				bool	   *argnull = sop->argnull;
  				xmltype    *data;
  				text	   *version;
  				int			standalone;
***************
*** 3239,3255 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  				Assert(!argnull[2]);	/* always present */
  				standalone = DatumGetInt32(argvalue[2]);
  
! 				*op->resvalue = PointerGetDatum(xmlroot(data,
  														version,
  														standalone));
! 				*op->resnull = false;
  			}
  			break;
  
  		case IS_XMLSERIALIZE:
  			{
! 				Datum	   *argvalue = op->d.xmlexpr.argvalue;
! 				bool	   *argnull = op->d.xmlexpr.argnull;
  
  				/* argument type is known to be xml */
  				Assert(list_length(xexpr->args) == 1);
--- 3396,3412 ----
  				Assert(!argnull[2]);	/* always present */
  				standalone = DatumGetInt32(argvalue[2]);
  
! 				*sop->resvalue = PointerGetDatum(xmlroot(data,
  														version,
  														standalone));
! 				*sop->resnull = false;
  			}
  			break;
  
  		case IS_XMLSERIALIZE:
  			{
! 				Datum	   *argvalue = sop->argvalue;
! 				bool	   *argnull = sop->argnull;
  
  				/* argument type is known to be xml */
  				Assert(list_length(xexpr->args) == 1);
***************
*** 3258,3274 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  					return;
  				value = argvalue[0];
  
! 				*op->resvalue = PointerGetDatum(
! 												xmltotext_with_xmloption(DatumGetXmlP(value),
! 																		 xexpr->xmloption));
! 				*op->resnull = false;
  			}
  			break;
  
  		case IS_DOCUMENT:
  			{
! 				Datum	   *argvalue = op->d.xmlexpr.argvalue;
! 				bool	   *argnull = op->d.xmlexpr.argnull;
  
  				/* optional argument is known to be xml */
  				Assert(list_length(xexpr->args) == 1);
--- 3415,3431 ----
  					return;
  				value = argvalue[0];
  
! 				*sop->resvalue = PointerGetDatum(
! 								                 xmltotext_with_xmloption(DatumGetXmlP(value),
! 														                  xexpr->xmloption));
! 				*sop->resnull = false;
  			}
  			break;
  
  		case IS_DOCUMENT:
  			{
! 				Datum	   *argvalue = sop->argvalue;
! 				bool	   *argnull = sop->argnull;
  
  				/* optional argument is known to be xml */
  				Assert(list_length(xexpr->args) == 1);
***************
*** 3277,3285 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
  					return;
  				value = argvalue[0];
  
! 				*op->resvalue =
  					BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
! 				*op->resnull = false;
  			}
  			break;
  
--- 3434,3442 ----
  					return;
  				value = argvalue[0];
  
! 				*sop->resvalue =
  					BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
! 				*sop->resnull = false;
  			}
  			break;
  
***************
*** 3299,3311 **** ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
   * grouping expressions in the current grouping set.
   */
  void
! ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op)
  {
  	int			result = 0;
! 	Bitmapset  *grouped_cols = op->d.grouping_func.parent->grouped_cols;
  	ListCell   *lc;
  
! 	foreach(lc, op->d.grouping_func.clauses)
  	{
  		int			attnum = lfirst_int(lc);
  
--- 3456,3468 ----
   * grouping expressions in the current grouping set.
   */
  void
! ExecEvalGroupingFunc(ExprState *state, ExprEvalStep_grouping_func *sop)
  {
  	int			result = 0;
! 	Bitmapset  *grouped_cols = sop->parent->grouped_cols;
  	ListCell   *lc;
  
! 	foreach(lc, sop->clauses)
  	{
  		int			attnum = lfirst_int(lc);
  
***************
*** 3315,3350 **** ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op)
  			result |= 1;
  	}
  
! 	*op->resvalue = Int32GetDatum(result);
! 	*op->resnull = false;
  }
  
  /*
   * Hand off evaluation of a subplan to nodeSubplan.c
   */
  void
! ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	SubPlanState *sstate = op->d.subplan.sstate;
  
  	/* could potentially be nested, so make sure there's enough stack */
  	check_stack_depth();
  
! 	*op->resvalue = ExecSubPlan(sstate, econtext, op->resnull);
  }
  
  /*
   * Hand off evaluation of an alternative subplan to nodeSubplan.c
   */
  void
! ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	AlternativeSubPlanState *asstate = op->d.alternative_subplan.asstate;
  
  	/* could potentially be nested, so make sure there's enough stack */
  	check_stack_depth();
  
! 	*op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
  }
  
  /*
--- 3472,3510 ----
  			result |= 1;
  	}
  
! 	*sop->resvalue = Int32GetDatum(result);
! 	*sop->resnull = false;
  }
  
  /*
   * Hand off evaluation of a subplan to nodeSubplan.c
   */
  void
! ExecEvalSubPlan(ExprState *state, ExprEvalStep_subplan *sop,
!                 ExprContext *econtext)
  {
! 	SubPlanState *sstate = sop->sstate;
  
  	/* could potentially be nested, so make sure there's enough stack */
  	check_stack_depth();
  
! 	*sop->resvalue = ExecSubPlan(sstate, econtext, sop->resnull);
  }
  
  /*
   * Hand off evaluation of an alternative subplan to nodeSubplan.c
   */
  void
! ExecEvalAlternativeSubPlan(ExprState *state,
!                            ExprEvalStep_alternative_subplan *sop,
! 						   ExprContext *econtext)
  {
! 	AlternativeSubPlanState *asstate = sop->asstate;
  
  	/* could potentially be nested, so make sure there's enough stack */
  	check_stack_depth();
  
! 	*sop->resvalue = ExecAlternativeSubPlan(asstate, econtext, sop->resnull);
  }
  
  /*
***************
*** 3354,3362 **** ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econ
   * with respect to given expression context.
   */
  void
! ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  {
! 	Var		   *variable = op->d.wholerow.var;
  	TupleTableSlot *slot;
  	TupleDesc	output_tupdesc;
  	MemoryContext oldcontext;
--- 3514,3523 ----
   * with respect to given expression context.
   */
  void
! ExecEvalWholeRowVar(ExprState *state, ExprEvalStep_wholerow *sop,
!                     ExprContext *econtext)
  {
! 	Var		   *variable = sop->var;
  	TupleTableSlot *slot;
  	TupleDesc	output_tupdesc;
  	MemoryContext oldcontext;
***************
*** 3388,3395 **** ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  	}
  
  	/* Apply the junkfilter if any */
! 	if (op->d.wholerow.junkFilter != NULL)
! 		slot = ExecFilterJunk(op->d.wholerow.junkFilter, slot);
  
  	/*
  	 * If first time through, obtain tuple descriptor and check compatibility.
--- 3549,3556 ----
  	}
  
  	/* Apply the junkfilter if any */
! 	if (sop->junkFilter != NULL)
! 		slot = ExecFilterJunk(sop->junkFilter, slot);
  
  	/*
  	 * If first time through, obtain tuple descriptor and check compatibility.
***************
*** 3398,3407 **** ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  	 * initialization phase, but due to using slots that's currently not
  	 * feasible.
  	 */
! 	if (op->d.wholerow.first)
  	{
  		/* optimistically assume we don't need slow path */
! 		op->d.wholerow.slow = false;
  
  		/*
  		 * If the Var identifies a named composite type, we must check that
--- 3559,3568 ----
  	 * initialization phase, but due to using slots that's currently not
  	 * feasible.
  	 */
! 	if (sop->first)
  	{
  		/* optimistically assume we don't need slow path */
! 		sop->slow = false;
  
  		/*
  		 * If the Var identifies a named composite type, we must check that
***************
*** 3457,3463 **** ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  
  				if (vattr->attlen != sattr->attlen ||
  					vattr->attalign != sattr->attalign)
! 					op->d.wholerow.slow = true; /* need to check for nulls */
  			}
  
  			/*
--- 3618,3624 ----
  
  				if (vattr->attlen != sattr->attlen ||
  					vattr->attalign != sattr->attalign)
! 					sop->slow = true; /* need to check for nulls */
  			}
  
  			/*
***************
*** 3518,3526 **** ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  		}
  
  		/* Bless the tupdesc if needed, and save it in the execution state */
! 		op->d.wholerow.tupdesc = BlessTupleDesc(output_tupdesc);
  
! 		op->d.wholerow.first = false;
  	}
  
  	/*
--- 3679,3687 ----
  		}
  
  		/* Bless the tupdesc if needed, and save it in the execution state */
! 		sop->tupdesc = BlessTupleDesc(output_tupdesc);
  
! 		sop->first = false;
  	}
  
  	/*
***************
*** 3529,3539 **** ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  	 */
  	slot_getallattrs(slot);
  
! 	if (op->d.wholerow.slow)
  	{
  		/* Check to see if any dropped attributes are non-null */
  		TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
! 		TupleDesc	var_tupdesc = op->d.wholerow.tupdesc;
  		int			i;
  
  		Assert(var_tupdesc->natts == tupleDesc->natts);
--- 3690,3700 ----
  	 */
  	slot_getallattrs(slot);
  
! 	if (sop->slow)
  	{
  		/* Check to see if any dropped attributes are non-null */
  		TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
! 		TupleDesc	var_tupdesc = sop->tupdesc;
  		int			i;
  
  		Assert(var_tupdesc->natts == tupleDesc->natts);
***************
*** 3570,3581 **** ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
  	/*
  	 * Label the datum with the composite type info we identified before.
  	 *
! 	 * (Note: we could skip doing this by passing op->d.wholerow.tupdesc to
  	 * the tuple build step; but that seems a tad risky so let's not.)
  	 */
! 	HeapTupleHeaderSetTypeId(dtuple, op->d.wholerow.tupdesc->tdtypeid);
! 	HeapTupleHeaderSetTypMod(dtuple, op->d.wholerow.tupdesc->tdtypmod);
  
! 	*op->resvalue = PointerGetDatum(dtuple);
! 	*op->resnull = false;
  }
--- 3731,3742 ----
  	/*
  	 * Label the datum with the composite type info we identified before.
  	 *
! 	 * (Note: we could skip doing this by passing sop->tupdesc to
  	 * the tuple build step; but that seems a tad risky so let's not.)
  	 */
! 	HeapTupleHeaderSetTypeId(dtuple, sop->tupdesc->tdtypeid);
! 	HeapTupleHeaderSetTypMod(dtuple, sop->tupdesc->tdtypmod);
  
! 	*sop->resvalue = PointerGetDatum(dtuple);
! 	*sop->resnull = false;
  }
*** a/src/include/executor/execExpr.h
--- b/src/include/executor/execExpr.h
***************
*** 217,568 **** typedef enum ExprEvalOp
  } ExprEvalOp;
  
  
  typedef struct ExprEvalStep
  {
! 	/*
! 	 * Instruction to be executed.  During instruction preparation this is an
! 	 * enum ExprEvalOp, but later it can be changed to some other type, e.g. a
! 	 * pointer for computed goto (that's why it's an intptr_t).
! 	 */
! 	intptr_t	opcode;
! 
! 	/* where to store the result of this step */
! 	Datum	   *resvalue;
! 	bool	   *resnull;
! 
! 	/*
! 	 * Inline data for the operation.  Inline data is faster to access, but
! 	 * also bloats the size of all instructions.  The union should be kept to
! 	 * no more than 40 bytes on 64-bit systems (so that the entire struct is
! 	 * no more than 64 bytes, a single cacheline on common systems).
! 	 */
! 	union
! 	{
! 		/* for EEOP_INNER/OUTER/SCAN_FETCHSOME */
! 		struct
! 		{
! 			/* attribute number up to which to fetch (inclusive) */
! 			int			last_var;
! 		}			fetch;
! 
! 		/* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */
! 		struct
! 		{
! 			/* attnum is attr number - 1 for regular VAR ... */
! 			/* but it's just the normal (negative) attr number for SYSVAR */
! 			int			attnum;
! 			Oid			vartype;	/* type OID of variable */
! 		}			var;
! 
! 		/* for EEOP_WHOLEROW */
! 		struct
! 		{
! 			Var		   *var;	/* original Var node in plan tree */
! 			bool		first;	/* first time through, need to initialize? */
! 			bool		slow;	/* need runtime check for nulls? */
! 			TupleDesc	tupdesc;	/* descriptor for resulting tuples */
! 			JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */
! 		}			wholerow;
! 
! 		/* for EEOP_ASSIGN_*_VAR */
! 		struct
! 		{
! 			/* target index in ExprState->resultslot->tts_values/nulls */
! 			int			resultnum;
! 			/* source attribute number - 1 */
! 			int			attnum;
! 		}			assign_var;
! 
! 		/* for EEOP_ASSIGN_TMP[_MAKE_RO] */
! 		struct
! 		{
! 			/* target index in ExprState->resultslot->tts_values/nulls */
! 			int			resultnum;
! 		}			assign_tmp;
! 
! 		/* for EEOP_CONST */
! 		struct
! 		{
! 			/* constant's value */
! 			Datum		value;
! 			bool		isnull;
! 		}			constval;
! 
! 		/* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
! 		struct
! 		{
! 			FmgrInfo   *finfo;	/* function's lookup data */
! 			FunctionCallInfo fcinfo_data;	/* arguments etc */
! 			/* faster to access without additional indirection: */
! 			PGFunction	fn_addr;	/* actual call address */
! 			int			nargs;	/* number of arguments */
! 		}			func;
! 
! 		/* for EEOP_BOOL_*_STEP */
! 		struct
! 		{
! 			bool	   *anynull;	/* track if any input was NULL */
! 			int			jumpdone;	/* jump here if result determined */
! 		}			boolexpr;
! 
! 		/* for EEOP_QUAL */
! 		struct
! 		{
! 			int			jumpdone;	/* jump here on false or null */
! 		}			qualexpr;
! 
! 		/* for EEOP_JUMP[_CONDITION] */
! 		struct
! 		{
! 			int			jumpdone;	/* target instruction's index */
! 		}			jump;
! 
! 		/* for EEOP_NULLTEST_ROWIS[NOT]NULL */
! 		struct
! 		{
! 			/* cached tupdesc pointer - filled at runtime */
! 			TupleDesc	argdesc;
! 		}			nulltest_row;
! 
! 		/* for EEOP_PARAM_EXEC/EXTERN */
! 		struct
! 		{
! 			int			paramid;	/* numeric ID for parameter */
! 			Oid			paramtype;	/* OID of parameter's datatype */
! 		}			param;
! 
! 		/* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
! 		struct
! 		{
! 			Datum	   *value;	/* value to return */
! 			bool	   *isnull;
! 		}			casetest;
! 
! 		/* for EEOP_MAKE_READONLY */
! 		struct
! 		{
! 			Datum	   *value;	/* value to coerce to read-only */
! 			bool	   *isnull;
! 		}			make_readonly;
! 
! 		/* for EEOP_IOCOERCE */
! 		struct
! 		{
! 			/* lookup and call info for source type's output function */
! 			FmgrInfo   *finfo_out;
! 			FunctionCallInfo fcinfo_data_out;
! 			/* lookup and call info for result type's input function */
! 			FmgrInfo   *finfo_in;
! 			FunctionCallInfo fcinfo_data_in;
! 		}			iocoerce;
! 
! 		/* for EEOP_SQLVALUEFUNCTION */
! 		struct
! 		{
! 			SQLValueFunction *svf;
! 		}			sqlvaluefunction;
! 
! 		/* for EEOP_NEXTVALUEEXPR */
! 		struct
! 		{
! 			Oid			seqid;
! 			Oid			seqtypid;
! 		}			nextvalueexpr;
! 
! 		/* for EEOP_ARRAYEXPR */
! 		struct
! 		{
! 			Datum	   *elemvalues; /* element values get stored here */
! 			bool	   *elemnulls;
! 			int			nelems; /* length of the above arrays */
! 			Oid			elemtype;	/* array element type */
! 			int16		elemlength; /* typlen of the array element type */
! 			bool		elembyval;	/* is the element type pass-by-value? */
! 			char		elemalign;	/* typalign of the element type */
! 			bool		multidims;	/* is array expression multi-D? */
! 		}			arrayexpr;
! 
! 		/* for EEOP_ARRAYCOERCE */
! 		struct
! 		{
! 			ArrayCoerceExpr *coerceexpr;
! 			Oid			resultelemtype; /* element type of result array */
! 			FmgrInfo   *elemfunc;	/* lookup info for element coercion
! 									 * function */
! 			struct ArrayMapState *amstate;	/* workspace for array_map */
! 		}			arraycoerce;
! 
! 		/* for EEOP_ROW */
! 		struct
! 		{
! 			TupleDesc	tupdesc;	/* descriptor for result tuples */
! 			/* workspace for the values constituting the row: */
! 			Datum	   *elemvalues;
! 			bool	   *elemnulls;
! 		}			row;
! 
! 		/* for EEOP_ROWCOMPARE_STEP */
! 		struct
! 		{
! 			/* lookup and call data for column comparison function */
! 			FmgrInfo   *finfo;
! 			FunctionCallInfo fcinfo_data;
! 			PGFunction	fn_addr;
! 			/* target for comparison resulting in NULL */
! 			int			jumpnull;
! 			/* target for comparison yielding inequality */
! 			int			jumpdone;
! 		}			rowcompare_step;
! 
! 		/* for EEOP_ROWCOMPARE_FINAL */
! 		struct
! 		{
! 			RowCompareType rctype;
! 		}			rowcompare_final;
! 
! 		/* for EEOP_MINMAX */
! 		struct
! 		{
! 			/* workspace for argument values */
! 			Datum	   *values;
! 			bool	   *nulls;
! 			int			nelems;
! 			/* is it GREATEST or LEAST? */
! 			MinMaxOp	op;
! 			/* lookup and call data for comparison function */
! 			FmgrInfo   *finfo;
! 			FunctionCallInfo fcinfo_data;
! 		}			minmax;
! 
! 		/* for EEOP_FIELDSELECT */
! 		struct
! 		{
! 			AttrNumber	fieldnum;	/* field number to extract */
! 			Oid			resulttype; /* field's type */
! 			/* cached tupdesc pointer - filled at runtime */
! 			TupleDesc	argdesc;
! 		}			fieldselect;
! 
! 		/* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */
! 		struct
! 		{
! 			/* original expression node */
! 			FieldStore *fstore;
! 
! 			/* cached tupdesc pointer - filled at runtime */
! 			/* note that a DEFORM and FORM pair share the same tupdesc */
! 			TupleDesc  *argdesc;
! 
! 			/* workspace for column values */
! 			Datum	   *values;
! 			bool	   *nulls;
! 			int			ncolumns;
! 		}			fieldstore;
! 
! 		/* for EEOP_ARRAYREF_SUBSCRIPT */
! 		struct
! 		{
! 			/* too big to have inline */
! 			struct ArrayRefState *state;
! 			int			off;	/* 0-based index of this subscript */
! 			bool		isupper;	/* is it upper or lower subscript? */
! 			int			jumpdone;	/* jump here on null */
! 		}			arrayref_subscript;
! 
! 		/* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */
! 		struct
! 		{
! 			/* too big to have inline */
! 			struct ArrayRefState *state;
! 		}			arrayref;
! 
! 		/* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */
! 		struct
! 		{
! 			/* name of constraint */
! 			char	   *constraintname;
! 			/* where the result of a CHECK constraint will be stored */
! 			Datum	   *checkvalue;
! 			bool	   *checknull;
! 			/* OID of domain type */
! 			Oid			resulttype;
! 		}			domaincheck;
! 
! 		/* for EEOP_CONVERT_ROWTYPE */
! 		struct
! 		{
! 			ConvertRowtypeExpr *convert;	/* original expression */
! 			/* these three fields are filled at runtime: */
! 			TupleDesc	indesc; /* tupdesc for input type */
! 			TupleDesc	outdesc;	/* tupdesc for output type */
! 			TupleConversionMap *map;	/* column mapping */
! 			bool		initialized;	/* initialized for current types? */
! 		}			convert_rowtype;
! 
! 		/* for EEOP_SCALARARRAYOP */
! 		struct
! 		{
! 			/* element_type/typlen/typbyval/typalign are filled at runtime */
! 			Oid			element_type;	/* InvalidOid if not yet filled */
! 			bool		useOr;	/* use OR or AND semantics? */
! 			int16		typlen; /* array element type storage info */
! 			bool		typbyval;
! 			char		typalign;
! 			FmgrInfo   *finfo;	/* function's lookup data */
! 			FunctionCallInfo fcinfo_data;	/* arguments etc */
! 			/* faster to access without additional indirection: */
! 			PGFunction	fn_addr;	/* actual call address */
! 		}			scalararrayop;
! 
! 		/* for EEOP_XMLEXPR */
! 		struct
! 		{
! 			XmlExpr    *xexpr;	/* original expression node */
! 			/* workspace for evaluating named args, if any */
! 			Datum	   *named_argvalue;
! 			bool	   *named_argnull;
! 			/* workspace for evaluating unnamed args, if any */
! 			Datum	   *argvalue;
! 			bool	   *argnull;
! 		}			xmlexpr;
! 
! 		/* for EEOP_AGGREF */
! 		struct
! 		{
! 			/* out-of-line state, modified by nodeAgg.c */
! 			AggrefExprState *astate;
! 		}			aggref;
! 
! 		/* for EEOP_GROUPING_FUNC */
! 		struct
! 		{
! 			AggState   *parent; /* parent Agg */
! 			List	   *clauses;	/* integer list of column numbers */
! 		}			grouping_func;
! 
! 		/* for EEOP_WINDOW_FUNC */
! 		struct
! 		{
! 			/* out-of-line state, modified by nodeWindowFunc.c */
! 			WindowFuncExprState *wfstate;
! 		}			window_func;
! 
! 		/* for EEOP_SUBPLAN */
! 		struct
! 		{
! 			/* out-of-line state, created by nodeSubplan.c */
! 			SubPlanState *sstate;
! 		}			subplan;
! 
! 		/* for EEOP_ALTERNATIVE_SUBPLAN */
! 		struct
! 		{
! 			/* out-of-line state, created by nodeSubplan.c */
! 			AlternativeSubPlanState *asstate;
! 		}			alternative_subplan;
! 	}			d;
  } ExprEvalStep;
  
  
  /* Non-inline data for array operations */
  typedef struct ArrayRefState
--- 217,648 ----
  } ExprEvalOp;
  
  
+ /*
+  * All ExecEvalStep* structures need to be interchangeable and start with
+  * the same fields. Use a #define to make the declarations consistent.
+  *
+  * The fields are:
+  *
+  *   opcode
+  *     Instruction to be executed.  During instruction preparation this is an
+  *     enum ExprEvalOp, but later it can be changed to some other type, e.g. a
+  *     pointer for computed goto (that's why it's an intptr_t).
+  *
+  *
+  *   resvalue
+  *   resnull
+  *     Where to store the result of the step.
+  *
+  *   nextstep
+  *     Next step to execute.
+  */
+ #define EXPR_EVAL_STEP_HEADER \
+    	intptr_t	         opcode;     \
+ 	Datum	            *resvalue;   \
+    	bool	            *resnull;    \
+    	struct ExprEvalStep *nextstep;
+ 
+ /* Generic step - all steps are castable to this type */
  typedef struct ExprEvalStep
  {
!    	EXPR_EVAL_STEP_HEADER;
  } ExprEvalStep;
  
+ /* for EEOP_INNER/OUTER/SCAN_FETCHSOME */
+ typedef struct ExprEvalStep_fetch
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* attribute number up to which to fetch (inclusive) */
+ 	int			last_var;
+ } ExprEvalStep_fetch;
+ 
+ /* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */
+ typedef struct ExprEvalStep_var
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* attnum is attr number - 1 for regular VAR ... */
+ 	/* but it's just the normal (negative) attr number for SYSVAR */
+ 	int			attnum;
+ 	Oid			vartype;	/* type OID of variable */
+ } ExprEvalStep_var;
+ 
+ /* for EEOP_WHOLEROW */
+ typedef struct ExprEvalStep_wholerow
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	Var		   *var;	/* original Var node in plan tree */
+ 	bool		first;	/* first time through, need to initialize? */
+ 	bool		slow;	/* need runtime check for nulls? */
+ 	TupleDesc	tupdesc;	/* descriptor for resulting tuples */
+ 	JunkFilter *junkFilter;		/* JunkFilter to remove resjunk cols */
+ } ExprEvalStep_wholerow;
+ 
+ /* for EEOP_ASSIGN_*_VAR */
+ typedef struct ExprEvalStep_assign_var
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* target index in ExprState->resultslot->tts_values/nulls */
+ 	int			resultnum;
+ 	/* source attribute number - 1 */
+ 	int			attnum;
+ } ExprEvalStep_assign_var;
+ 
+ /* for EEOP_ASSIGN_TMP[_MAKE_RO] */
+ typedef struct ExprEvalStep_assign_tmp
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* target index in ExprState->resultslot->tts_values/nulls */
+ 	int			resultnum;
+ } ExprEvalStep_assign_tmp;
+ 
+ /* for EEOP_CONST */
+ typedef struct ExprEvalStep_constval
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* constant's value */
+ 	Datum		value;
+ 	bool		isnull;
+ } ExprEvalStep_constval;
+ 
+ /* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
+ typedef struct ExprEvalStep_func
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	FmgrInfo   *finfo;	/* function's lookup data */
+ 	FunctionCallInfo fcinfo_data;		/* arguments etc */
+ 	/* faster to access without additional indirection: */
+ 	PGFunction	fn_addr;	/* actual call address */
+ 	int			nargs;	/* number of arguments */
+ } ExprEvalStep_func;
+ 
+ /* for EEOP_BOOL_*_STEP */
+ typedef struct ExprEvalStep_boolexpr
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	bool	     *anynull;	/* track if any input was NULL */
+ 	ExprEvalStep *jumpdone;		/* jump here if result determined */
+ } ExprEvalStep_boolexpr;
+ 
+ /* for EEOP_QUAL */
+ typedef struct ExprEvalStep_qualexpr
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	ExprEvalStep *jumpdone;		/* jump here on false or null */
+ } ExprEvalStep_qualexpr;
+ 
+ /* for EEOP_JUMP[_CONDITION] */
+ typedef struct ExprEvalStep_jump
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	ExprEvalStep *jumpdone;		/* target instruction's index */
+ } ExprEvalStep_jump;
+ 
+ /* for EEOP_NULLTEST_ROWIS[NOT]NULL */
+ typedef struct ExprEvalStep_nulltest_row
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* cached tupdesc pointer - filled at runtime */
+ 	TupleDesc	argdesc;
+ } ExprEvalStep_nulltest_row;
+ 
+ /* for EEOP_PARAM_EXEC/EXTERN */
+ typedef struct ExprEvalStep_param
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	int			paramid;	/* numeric ID for parameter */
+ 	Oid			paramtype;		/* OID of parameter's datatype */
+ } ExprEvalStep_param;
+ 
+ /* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
+ typedef struct ExprEvalStep_casetest
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	Datum	   *value;	/* value to return */
+ 	bool	   *isnull;
+ } ExprEvalStep_casetest;
+ 
+ /* for EEOP_MAKE_READONLY */
+ typedef struct ExprEvalStep_make_readonly
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	Datum	   *value;	/* value to coerce to read-only */
+ 	bool	   *isnull;
+ } ExprEvalStep_make_readonly;
+ 
+ /* for EEOP_IOCOERCE */
+ typedef struct ExprEvalStep_iocoerce
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* lookup and call info for source type's output function */
+ 	FmgrInfo   *finfo_out;
+ 	FunctionCallInfo fcinfo_data_out;
+ 	/* lookup and call info for result type's input function */
+ 	FmgrInfo   *finfo_in;
+ 	FunctionCallInfo fcinfo_data_in;
+ } ExprEvalStep_iocoerce;
+ 
+ /* for EEOP_SQLVALUEFUNCTION */
+ typedef struct ExprEvalStep_sqlvaluefunction
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	SQLValueFunction *svf;
+ } ExprEvalStep_sqlvaluefunction;
+ 
+ /* for EEOP_NEXTVALUEEXPR */
+ typedef struct ExprEvalStep_nextvalueexpr
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	Oid			seqid;
+ 	Oid			seqtypid;
+ } ExprEvalStep_nextvalueexpr;
+ 
+ /* for EEOP_ARRAYEXPR */
+ typedef struct ExprEvalStep_arrayexpr
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	Datum	   *elemvalues;		/* element values get stored here */
+ 	bool	   *elemnulls;
+ 	int			nelems; /* length of the above arrays */
+ 	Oid			elemtype;		/* array element type */
+ 	int16		elemlength;		/* typlen of the array element type */
+ 	bool		elembyval;		/* is the element type pass-by-value? */
+ 	char		elemalign;		/* typalign of the element type */
+ 	bool		multidims;		/* is array expression multi-D? */
+ } ExprEvalStep_arrayexpr;
+ 
+ /* for EEOP_ARRAYCOERCE */
+ typedef struct ExprEvalStep_arraycoerce
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	ArrayCoerceExpr *coerceexpr;
+ 	Oid			resultelemtype; /* element type of result array */
+ 	FmgrInfo   *elemfunc;		/* lookup info for element coercion
+ 								 * function */
+ 	struct ArrayMapState *amstate;		/* workspace for array_map */
+ } ExprEvalStep_arraycoerce;
+ 
+ /* for EEOP_ROW */
+ typedef struct ExprEvalStep_row
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	TupleDesc	tupdesc;	/* descriptor for result tuples */
+ 	/* workspace for the values constituting the row: */
+ 	Datum	   *elemvalues;
+ 	bool	   *elemnulls;
+ } ExprEvalStep_row;
+ 
+ /* for EEOP_ROWCOMPARE_STEP */
+ typedef struct ExprEvalStep_rowcompare_step
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* lookup and call data for column comparison function */
+ 	FmgrInfo   *finfo;
+ 	FunctionCallInfo fcinfo_data;
+ 	PGFunction	fn_addr;
+ 	/* target for comparison resulting in NULL */
+ 	ExprEvalStep *jumpnull;
+ 	/* target for comparison yielding inequality */
+ 	ExprEvalStep *jumpdone;
+ } ExprEvalStep_rowcompare_step;
+ 
+ /* for EEOP_ROWCOMPARE_FINAL */
+ typedef struct ExprEvalStep_rowcompare_final
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	RowCompareType rctype;
+ } ExprEvalStep_rowcompare_final;
+ 
+ /* for EEOP_MINMAX */
+ typedef struct ExprEvalStep_minmax
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* workspace for argument values */
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	int			nelems;
+ 	/* is it GREATEST or LEAST? */
+ 	MinMaxOp	op;
+ 	/* lookup and call data for comparison function */
+ 	FmgrInfo   *finfo;
+ 	FunctionCallInfo fcinfo_data;
+ } ExprEvalStep_minmax;
+ 
+ /* for EEOP_FIELDSELECT */
+ typedef struct ExprEvalStep_fieldselect
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	AttrNumber	fieldnum;		/* field number to extract */
+ 	Oid			resulttype;		/* field's type */
+ 	/* cached tupdesc pointer - filled at runtime */
+ 	TupleDesc	argdesc;
+ } ExprEvalStep_fieldselect;
+ 
+ /* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */
+ typedef struct ExprEvalStep_fieldstore
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* original expression node */
+ 	FieldStore *fstore;
+ 
+ 	/* cached tupdesc pointer - filled at runtime */
+ 	/* note that a DEFORM and FORM pair share the same tupdesc */
+ 	TupleDesc  *argdesc;
+ 
+ 	/* workspace for column values */
+ 	Datum	   *values;
+ 	bool	   *nulls;
+ 	int			ncolumns;
+ } ExprEvalStep_fieldstore;
+ 
+ /* for EEOP_ARRAYREF_SUBSCRIPT */
+ typedef struct ExprEvalStep_arrayref_subscript
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* too big to have inline */
+ 	struct ArrayRefState *state;
+ 	int			off;	/* 0-based index of this subscript */
+ 	bool		isupper;	/* is it upper or lower subscript? */
+ 	ExprEvalStep *jumpdone;		/* jump here on null */
+ } ExprEvalStep_arrayref_subscript;
+ 
+ /* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */
+ typedef struct ExprEvalStep_arrayref
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* too big to have inline */
+ 	struct ArrayRefState *state;
+ } ExprEvalStep_arrayref;
+ 
+ /* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */
+ typedef struct ExprEvalStep_domaincheck
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* name of constraint */
+ 	char	   *constraintname;
+ 	/* where the result of a CHECK constraint will be stored */
+ 	Datum	   *checkvalue;
+ 	bool	   *checknull;
+ 	/* OID of domain type */
+ 	Oid			resulttype;
+ } ExprEvalStep_domaincheck;
+ 
+ /* for EEOP_CONVERT_ROWTYPE */
+ typedef struct ExprEvalStep_convert_rowtype
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	ConvertRowtypeExpr *convert;		/* original expression */
+ 	/* these three fields are filled at runtime: */
+ 	TupleDesc	indesc; /* tupdesc for input type */
+ 	TupleDesc	outdesc;	/* tupdesc for output type */
+ 	TupleConversionMap *map;	/* column mapping */
+ 	bool		initialized;	/* initialized for current types? */
+ } ExprEvalStep_convert_rowtype;
+ 
+ /* for EEOP_SCALARARRAYOP */
+ typedef struct ExprEvalStep_scalararrayop
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* element_type/typlen/typbyval/typalign are filled at runtime */
+ 	Oid			element_type;	/* InvalidOid if not yet filled */
+ 	bool		useOr;	/* use OR or AND semantics? */
+ 	int16		typlen; /* array element type storage info */
+ 	bool		typbyval;
+ 	char		typalign;
+ 	FmgrInfo   *finfo;	/* function's lookup data */
+ 	FunctionCallInfo fcinfo_data;		/* arguments etc */
+ 	/* faster to access without additional indirection: */
+ 	PGFunction	fn_addr;	/* actual call address */
+ } ExprEvalStep_scalararrayop;
+ 
+ /* for EEOP_XMLEXPR */
+ typedef struct ExprEvalStep_xmlexpr
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	XmlExpr    *xexpr;	/* original expression node */
+ 	/* workspace for evaluating named args, if any */
+ 	Datum	   *named_argvalue;
+ 	bool	   *named_argnull;
+ 	/* workspace for evaluating unnamed args, if any */
+ 	Datum	   *argvalue;
+ 	bool	   *argnull;
+ } ExprEvalStep_xmlexpr;
+ 
+ /* for EEOP_AGGREF */
+ typedef struct ExprEvalStep_aggref
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* out-of-line state, modified by nodeAgg.c */
+ 	AggrefExprState *astate;
+ } ExprEvalStep_aggref;
+ 
+ /* for EEOP_GROUPING_FUNC */
+ typedef struct ExprEvalStep_grouping_func
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	AggState   *parent; /* parent Agg */
+ 	List	   *clauses;	/* integer list of column numbers */
+ } ExprEvalStep_grouping_func;
+ 
+ /* for EEOP_WINDOW_FUNC */
+ typedef struct ExprEvalStep_window_func
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* out-of-line state, modified by nodeWindowFunc.c */
+ 	WindowFuncExprState *wfstate;
+ } ExprEvalStep_window_func;
+ 
+ /* for EEOP_SUBPLAN */
+ typedef struct ExprEvalStep_subplan
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* out-of-line state, created by nodeSubplan.c */
+ 	SubPlanState *sstate;
+ } ExprEvalStep_subplan;
+ 
+ /* for EEOP_ALTERNATIVE_SUBPLAN */
+ typedef struct ExprEvalStep_alternative_subplan
+ {
+    	EXPR_EVAL_STEP_HEADER;
+ 
+ 	/* out-of-line state, created by nodeSubplan.c */
+ 	AlternativeSubPlanState *asstate;
+ } ExprEvalStep_alternative_subplan;
+ 
  
  /* Non-inline data for array operations */
  typedef struct ArrayRefState
***************
*** 609,651 **** extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
   * execExprInterp.c, because that allows them to be used by other methods of
   * expression evaluation, reducing code duplication.
   */
! extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
  				  ExprContext *econtext);
! extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
  					ExprContext *econtext);
! extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op,
  				ExprContext *econtext);
! extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op,
  				   ExprContext *econtext);
! extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalRow(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op,
  					ExprContext *econtext);
! extern void ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op,
! 						 ExprContext *econtext);
! extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
  					   ExprContext *econtext);
! extern bool ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
! 					   ExprContext *econtext);
! extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
! extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
  				ExprContext *econtext);
! extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op,
  						   ExprContext *econtext);
! extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
  					ExprContext *econtext);
  
  #endif							/* EXEC_EXPR_H */
--- 689,741 ----
   * execExprInterp.c, because that allows them to be used by other methods of
   * expression evaluation, reducing code duplication.
   */
! extern void ExecEvalParamExec(ExprState *state, ExprEvalStep_param *sop,
  				  ExprContext *econtext);
! extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep_param *sop,
  					ExprContext *econtext);
! extern void ExecEvalSQLValueFunction(ExprState *state,
!                     ExprEvalStep_sqlvaluefunction *sop);
! extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *sop);
! extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep_nextvalueexpr *sop);
! extern void ExecEvalRowNull(ExprState *state, ExprEvalStep_nulltest_row *sop,
  				ExprContext *econtext);
! extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep_nulltest_row *sop,
  				   ExprContext *econtext);
! extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep_arrayexpr *sop);
! extern void ExecEvalArrayCoerce(ExprState *state,
!                     ExprEvalStep_arraycoerce *sop);
! extern void ExecEvalRow(ExprState *state, ExprEvalStep_row *sop);
! extern void ExecEvalMinMax(ExprState *state, ExprEvalStep_minmax *sop);
! extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep_fieldselect *sop,
  					ExprContext *econtext);
! extern void ExecEvalFieldStoreDeForm(ExprState *state,
!                          ExprEvalStep_fieldstore *sop, ExprContext *econtext);
! extern void ExecEvalFieldStoreForm(ExprState *state,
!                          ExprEvalStep_fieldstore *sop, ExprContext *econtext);
! extern bool ExecEvalArrayRefSubscript(ExprState *state,
!                     ExprEvalStep_arrayref_subscript *sop);
! extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep_arrayref *sop);
! extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep_arrayref *sop);
! extern void ExecEvalArrayRefAssign(ExprState *state,
!                     ExprEvalStep_arrayref *sop);
! extern void ExecEvalConvertRowtype(ExprState *state,
!                        ExprEvalStep_convert_rowtype *sop,
  					   ExprContext *econtext);
! extern void ExecEvalScalarArrayOp(ExprState *state,
!                     ExprEvalStep_scalararrayop *sop);
! extern void ExecEvalConstraintNotNull(ExprState *state,
!                     ExprEvalStep_domaincheck *sop);
! extern void ExecEvalConstraintCheck(ExprState *state,
!                     ExprEvalStep_domaincheck *sop);
! extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep_xmlexpr *sop);
! extern void ExecEvalGroupingFunc(ExprState *state,
!                     ExprEvalStep_grouping_func *sop);
! extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep_subplan *sop,
  				ExprContext *econtext);
! extern void ExecEvalAlternativeSubPlan(ExprState *state,
!                            ExprEvalStep_alternative_subplan *sop,
  						   ExprContext *econtext);
! extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep_wholerow *sop,
  					ExprContext *econtext);
  
  #endif							/* EXEC_EXPR_H */
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 72,77 **** typedef struct ExprState
--- 72,78 ----
  	 * Instructions to compute expression's return value.
  	 */
  	struct ExprEvalStep *steps;
+ 	int                  num_steps;
  
  	/*
  	 * Function that actually evaluates the expression.  This can be set to
***************
*** 86,93 **** typedef struct ExprState
  	 * XXX: following only needed during "compilation", could be thrown away.
  	 */
  
! 	int			steps_len;		/* number of steps currently */
! 	int			steps_alloc;	/* allocated length of steps array */
  
  	Datum	   *innermost_caseval;
  	bool	   *innermost_casenull;
--- 87,101 ----
  	 * XXX: following only needed during "compilation", could be thrown away.
  	 */
  
! 	struct ExprEvalStep *last_step; /* Last step created */
! 	char                *step_buffer; /* Memory buffer for steps */
! 	size_t               buffer_size; /* Size of remaining buffer */
! 	List                *adjust_jumps; /* "jumpdone" pointers to be resolved
! 	                                      when the next operator is created.
! 										  Generally, this list should be
! 										  created as a local variable and only
! 										  attached here when it is time to
! 										  resolve the pointers. */
  
  	Datum	   *innermost_caseval;
  	bool	   *innermost_casenull;


^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Tightly packing expressions
@ 2017-08-03 18:14  Andres Freund <[email protected]>
  parent: Douglas Doole <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Andres Freund @ 2017-08-03 18:14 UTC (permalink / raw)
  To: Douglas Doole <[email protected]>; +Cc: pgsql-hackers

Hi Doug,

On 2017-08-03 18:01:06 +0000, Douglas Doole wrote:
> Back when you were getting ready to commit your faster expressions, I
> volunteered to look at switching from an array of fixed sized steps to
> tightly packed structures. (
> https://www.postgresql.org/message-id/[email protected]).
> I've finally found time to make it happen.

Thanks for working on it!


> Using tightly packed structures makes the expressions a lot smaller. I
> instrumented some before and after builds and ran "make check" just to see
> how much memory expressions were using. What I found was:
> 
> There were ~104K expressions.
> 
> Memory - bytes needed to hold the steps of the expressions
> Allocated Memory - memory is allocated in blocks, this is the bytes
> allocated to hold the expressions
> Wasted Memory - the difference between the allocated and used memory
> 
> Original code:
> Memory: min=64 max=9984 total=28232512 average=265
> Allocated Memory: min=1024 max=16384 total=111171584 average=1045
> Wasted Memory: min=0 max=6400 total=82939072 average=780
> 
> New code:
> Memory: min=32 (50%) max=8128 (82%) total=18266712 (65%) average=175 (66%)
> Allocated Memory: min=192 (19%) max=9408 (57%) total=29386176 (26%)
> average=282 (27%)
> Wasted Memory: min=0 (0%) max=1280 (20%) total=11119464 (13%) average=106
> (14%)

That's actually not *that* big of a saving...


> Another benefit of the way I did this is that the expression memory is
> never reallocated. This means that it is safe for one step to point
> directly to a field in another step without needing to separately palloc
> storage. That should allow us to simplify the code and reduce pointer
> traversals. (I haven't exploited this in the patch. I figured it would be a
> task for round 2 assuming you like what I've done here.)

Yes, that's a neat benefit. Although I think it'd even better if we
could work to the point where the mutable and the unchanging data is
separately allocated, so we can at some point avoid redundant expression
"compilations".


> The work was mostly mechanical. The only tricky bit was dealing with the
> places where you jump to step n+1 while building step n. Since we can't
> tell the address of step n+1 until it is actually built, I had to defer
> resolution of the jumps. So all the interesting bits of this patch are in
> ExprEvalPushStep().

I was wondering about a more general "symbol resolution" stage
anyway. Then we'd allocate individual steps during ExecInitExprRec, and
allocate the linear array after we know the exact size.


I think it'd be important to see some performance measurements,
especially for larger queries. It'd not be too surprising if the
different method of dereffing the next expression actually has a
negative performance effect, but I'm absolutely not sure of that.

Regards,

Andres


-- 
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH 2/2] Propagate replica identity to partitions
@ 2019-02-04 16:43  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Alvaro Herrera @ 2019-02-04 16:43 UTC (permalink / raw)

---
 src/backend/commands/tablecmds.c               | 59 +++++++++++++--
 src/bin/psql/describe.c                        |  3 +-
 src/test/regress/expected/replica_identity.out | 99 ++++++++++++++++++++++++++
 src/test/regress/sql/replica_identity.sql      | 33 +++++++++
 4 files changed, 189 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 877bac506f..4b2ae01f15 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -300,7 +300,7 @@ static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 							Oid relId, Oid oldRelId, void *arg);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supconstr);
+				bool is_partition, List **supconstr, char *ri_type);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -485,6 +485,7 @@ static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel);
 static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 								   List *partConstraint,
 								   bool validate_default);
+static void MatchReplicaIdentity(Relation tgtrel, Relation srcrel);
 static void CloneRowTriggersToPartition(Relation parent, Relation partition);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
@@ -527,6 +528,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	TupleDesc	descriptor;
 	List	   *inheritOids;
 	List	   *old_constraints;
+	char		ri_type;
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
@@ -708,7 +710,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		MergeAttributes(stmt->tableElts, inheritOids,
 						stmt->relation->relpersistence,
 						stmt->partbound != NULL,
-						&old_constraints);
+						&old_constraints, &ri_type);
 
 	/*
 	 * Create a tuple descriptor from the relation schema.  Note that this
@@ -1014,6 +1016,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		CloneForeignKeyConstraints(parentId, relationId, NULL);
 
+		/* Propagate REPLICA IDENTITY information too */
+		if (ri_type != REPLICA_IDENTITY_DEFAULT)
+			MatchReplicaIdentity(rel, parent);
+
 		table_close(parent, NoLock);
 	}
 
@@ -1873,6 +1879,8 @@ storage_name(char c)
  * Output arguments:
  * 'supconstr' receives a list of constraints belonging to the parents,
  *		updated as necessary to be valid for the child.
+ * 'ri_type' receives the replica identity type of the last parent seen,
+ *		or default if none.
  *
  * Return value:
  * Completed schema list.
@@ -1914,11 +1922,15 @@ storage_name(char c)
  *		(4) Otherwise the inherited default is used.
  *		Rule (3) is new in Postgres 7.1; in earlier releases you got a
  *		rather arbitrary choice of which parent default to use.
+ *
+ *	  It only makes sense to use the returned 'ri_type' when there's a single
+ *	  parent, such as in declarative partitioning.
  *----------
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supconstr)
+				bool is_partition, List **supconstr,
+				char *ri_type)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -2015,6 +2027,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Initialize replica identity to default; parents may change it later
+	 */
+	*ri_type = REPLICA_IDENTITY_DEFAULT;
+
+	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
 	 * list of inherited attributes (inhSchema).  Also check to see if we need
 	 * to inherit an OID column.
@@ -2095,6 +2112,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							? "cannot inherit from temporary relation of another session"
 							: "cannot create as partition of temporary relation of another session")));
 
+		/* Indicate replica identity back to caller */
+		*ri_type = relation->rd_rel->relreplident;
+
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
 		 * demand that creator of a child table own the parent.
@@ -3935,7 +3955,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_ReplicaIdentity:	/* REPLICA IDENTITY ... */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
 			pass = AT_PASS_MISC;
-			/* This command never recurses */
+			/* Recurse only on partitioned tables */
+			if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+				ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
 			/* No command-specific prep needed */
 			break;
 		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
@@ -14664,6 +14686,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* and triggers */
 	CloneRowTriggersToPartition(rel, attachrel);
 
+	/* Propagate REPLICA IDENTITY information */
+	if (rel->rd_rel->relreplident != REPLICA_IDENTITY_DEFAULT)
+		MatchReplicaIdentity(attachrel, rel);
+
 	/*
 	 * Clone foreign key constraints, and setup for Phase 3 to verify them.
 	 */
@@ -14915,6 +14941,31 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 }
 
 /*
+ * Set up partRel's (a partition) replica identity to match parentRel's (its
+ * parent).
+ */
+static void
+MatchReplicaIdentity(Relation partRel, Relation srcrel)
+{
+	Oid		ri_index;
+
+	if (srcrel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX)
+	{
+		ri_index = index_get_partition(partRel,
+									   RelationGetReplicaIndex(srcrel));
+		if (!OidIsValid(ri_index))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("replica index does not exist in partition")));
+	}
+	else
+		ri_index = InvalidOid;
+
+	relation_mark_replica_identity(partRel, srcrel->rd_rel->relreplident,
+								   ri_index, true);
+}
+
+/*
  * CloneRowTriggersToPartition
  *		subroutine for ATExecAttachPartition/DefineRelation to create row
  *		triggers on partitions
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4da6719ce7..6145a000cb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3113,7 +3113,8 @@ describeOneTableDetails(const char *schemaname,
 
 		if (verbose &&
 			(tableinfo.relkind == RELKIND_RELATION ||
-			 tableinfo.relkind == RELKIND_MATVIEW) &&
+			 tableinfo.relkind == RELKIND_MATVIEW ||
+			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE) &&
 
 		/*
 		 * No need to display default values; we already display a REPLICA
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 175ecd2879..3051cf1551 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -181,3 +181,102 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity_othertable;
+----
+-- Make sure it propagates to partitions
+----
+CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (1000) TO (2000);
+CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1
+  FOR VALUES FROM (1000) TO (1500);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (2000) TO (3000);
+CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part);
+ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4
+  FOR VALUES FROM (3000) TO (4000);
+\d+ test_replica_identity_part2
+                        Table "public.test_replica_identity_part2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part11
+                       Table "public.test_replica_identity_part11"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500)
+Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part
+                  Partitioned table "public.test_replica_identity_part"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition key: RANGE (a)
+Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED,
+            test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000),
+            test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000),
+            test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000)
+Replica Identity: FULL
+
+\d+ test_replica_identity_part3
+                        Table "public.test_replica_identity_part3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part4
+                        Table "public.test_replica_identity_part4"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000))
+Replica Identity: FULL
+
+----
+-- Check behavior with inherited tables
+----
+CREATE TABLE test_replica_identity_inh (a int);
+CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh);
+ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh);
+\d+ test_replica_identity_inh
+                         Table "public.test_replica_identity_inh"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Child tables: test_replica_identity_cld,
+              test_replica_identity_cld2
+Replica Identity: FULL
+
+\d+ test_replica_identity_cld
+                         Table "public.test_replica_identity_cld"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Inherits: test_replica_identity_inh
+
+\d+ test_replica_identity_cld2
+                        Table "public.test_replica_identity_cld2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Inherits: test_replica_identity_inh
+
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index b08a3623b8..a567f2b52d 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -77,3 +77,36 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity_othertable;
+
+----
+-- Make sure it propagates to partitions
+----
+CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (1000) TO (2000);
+CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1
+  FOR VALUES FROM (1000) TO (1500);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (2000) TO (3000);
+CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part);
+ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4
+  FOR VALUES FROM (3000) TO (4000);
+\d+ test_replica_identity_part2
+\d+ test_replica_identity_part11
+\d+ test_replica_identity_part
+\d+ test_replica_identity_part3
+\d+ test_replica_identity_part4
+
+----
+-- Check behavior with inherited tables
+----
+CREATE TABLE test_replica_identity_inh (a int);
+CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh);
+ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh);
+\d+ test_replica_identity_inh
+\d+ test_replica_identity_cld
+\d+ test_replica_identity_cld2
-- 
2.11.0


--gy7na3sjffpqghel--




^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v3 2/2] Propagate replica identity to partitions
@ 2019-02-04 16:43  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Alvaro Herrera @ 2019-02-04 16:43 UTC (permalink / raw)

---
 src/backend/commands/tablecmds.c               | 132 ++++++++++++++++++--
 src/bin/psql/describe.c                        |   3 +-
 src/test/regress/expected/replica_identity.out | 160 +++++++++++++++++++++++++
 src/test/regress/sql/replica_identity.sql      |  43 +++++++
 4 files changed, 326 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 877bac506f..b26872e78f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -300,7 +300,7 @@ static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 							Oid relId, Oid oldRelId, void *arg);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supconstr);
+				bool is_partition, List **supconstr, char *ri_type);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -485,6 +485,7 @@ static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel);
 static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 								   List *partConstraint,
 								   bool validate_default);
+static void MatchReplicaIdentity(Relation tgtrel, Relation srcrel);
 static void CloneRowTriggersToPartition(Relation parent, Relation partition);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
@@ -527,6 +528,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	TupleDesc	descriptor;
 	List	   *inheritOids;
 	List	   *old_constraints;
+	char		ri_type;
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
@@ -708,7 +710,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		MergeAttributes(stmt->tableElts, inheritOids,
 						stmt->relation->relpersistence,
 						stmt->partbound != NULL,
-						&old_constraints);
+						&old_constraints, &ri_type);
 
 	/*
 	 * Create a tuple descriptor from the relation schema.  Note that this
@@ -1014,6 +1016,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		CloneForeignKeyConstraints(parentId, relationId, NULL);
 
+		/* Propagate REPLICA IDENTITY information too */
+		if (ri_type != REPLICA_IDENTITY_DEFAULT)
+			MatchReplicaIdentity(rel, parent);
+
 		table_close(parent, NoLock);
 	}
 
@@ -1873,6 +1879,8 @@ storage_name(char c)
  * Output arguments:
  * 'supconstr' receives a list of constraints belonging to the parents,
  *		updated as necessary to be valid for the child.
+ * 'ri_type' receives the replica identity type of the last parent seen,
+ *		or default if none.
  *
  * Return value:
  * Completed schema list.
@@ -1914,11 +1922,15 @@ storage_name(char c)
  *		(4) Otherwise the inherited default is used.
  *		Rule (3) is new in Postgres 7.1; in earlier releases you got a
  *		rather arbitrary choice of which parent default to use.
+ *
+ *	  It only makes sense to use the returned 'ri_type' when there's a single
+ *	  parent, such as in declarative partitioning.
  *----------
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supconstr)
+				bool is_partition, List **supconstr,
+				char *ri_type)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -2015,6 +2027,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Initialize replica identity to default; parents may change it later
+	 */
+	*ri_type = REPLICA_IDENTITY_DEFAULT;
+
+	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
 	 * list of inherited attributes (inhSchema).  Also check to see if we need
 	 * to inherit an OID column.
@@ -2095,6 +2112,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							? "cannot inherit from temporary relation of another session"
 							: "cannot create as partition of temporary relation of another session")));
 
+		/* Indicate replica identity back to caller */
+		*ri_type = relation->rd_rel->relreplident;
+
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
 		 * demand that creator of a child table own the parent.
@@ -3935,7 +3955,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_ReplicaIdentity:	/* REPLICA IDENTITY ... */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
 			pass = AT_PASS_MISC;
-			/* This command never recurses */
+			/* Recursion occurs during execution phase */
 			/* No command-specific prep needed */
 			break;
 		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
@@ -12753,10 +12773,13 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
  *
  * Iff ri_type = REPLICA_IDENTITY_INDEX, indexOid must be the Oid of a suitable
  * index. Otherwise, it should be InvalidOid.
+ *
+ * 'permissive' means to ignore the case where a partitioned table contains
+ * a partition without the index.  If false, raise an error.
  */
 static void
 relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
-							   bool is_internal)
+							   bool is_internal, bool permissive, LOCKMODE lockmode)
 {
 	Relation	pg_index;
 	Relation	pg_class;
@@ -12847,6 +12870,58 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
 	}
 
 	table_close(pg_index, RowExclusiveLock);
+
+	/*
+	 * If there are any partitions, handle them too.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDesc	pd = RelationGetPartitionDesc(rel);
+
+		for (int i = 0; i < pd->nparts; i++)
+		{
+			Relation	part = table_open(pd->oids[i], lockmode);
+			Oid			idxOid;
+
+			if (ri_type == REPLICA_IDENTITY_INDEX)
+			{
+				/*
+				 * When using an index as identity, and some partition does not
+				 * have the index, we either raise an error (if not in
+				 * "permissive" mode), or ignore the partition; this happens
+				 * when the index hierarchy is being restored by pg_dump.  The
+				 * only thing we can do at this point in that case is hope that
+				 * there will be another command creating the right index soon.
+				 */
+				idxOid = index_get_partition(part, indexOid);
+				if (!OidIsValid(idxOid))
+				{
+					if (permissive)
+					{
+						table_close(part, NoLock);
+						continue;
+					}
+					else
+						ereport(ERROR,
+								(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+								 errmsg("replica index does not exist in partition \"%s\"",
+										RelationGetRelationName(part))));
+				}
+
+				LockRelationOid(idxOid, ShareLock);
+			}
+			else
+				idxOid = InvalidOid;
+
+			idxOid = ri_type == REPLICA_IDENTITY_INDEX ?
+				index_get_partition(part, indexOid) : InvalidOid;
+
+			relation_mark_replica_identity(part, ri_type, idxOid, true,
+										   permissive, lockmode);
+
+			table_close(part, NoLock);
+		}
+	}
 }
 
 /*
@@ -12861,17 +12936,20 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 
 	if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
 	{
-		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true,
+									   false, lockmode);
 		return;
 	}
 	else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
 	{
-		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true,
+									   false, lockmode);
 		return;
 	}
 	else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
 	{
-		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true,
+									   false, lockmode);
 		return;
 	}
 	else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
@@ -12925,8 +13003,9 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot use partial index \"%s\" as replica identity",
 						RelationGetRelationName(indexRel))));
-	/* And neither are invalid indexes. */
-	if (!indexRel->rd_index->indisvalid)
+	/* And neither are invalid indexes, except on partitioned tables. */
+	if (!indexRel->rd_index->indisvalid &&
+		rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot use invalid index \"%s\" as replica identity",
@@ -12959,7 +13038,8 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	}
 
 	/* This index is suitable for use as a replica identity. Mark it. */
-	relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
+	relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true,
+								   true, lockmode);
 
 	index_close(indexRel, NoLock);
 }
@@ -14664,6 +14744,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* and triggers */
 	CloneRowTriggersToPartition(rel, attachrel);
 
+	/* Propagate REPLICA IDENTITY information */
+	if (rel->rd_rel->relreplident != REPLICA_IDENTITY_DEFAULT)
+		MatchReplicaIdentity(attachrel, rel);
+
 	/*
 	 * Clone foreign key constraints, and setup for Phase 3 to verify them.
 	 */
@@ -14915,6 +14999,32 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 }
 
 /*
+ * Set up partRel's (a partition) replica identity to match parentRel's (its
+ * parent).
+ */
+static void
+MatchReplicaIdentity(Relation partRel, Relation srcrel)
+{
+	Oid		ri_index;
+
+	if (srcrel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX)
+	{
+		ri_index = index_get_partition(partRel,
+									   RelationGetReplicaIndex(srcrel));
+		if (!OidIsValid(ri_index))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("replica index does not exist in partition \"%s\"",
+							RelationGetRelationName(partRel))));
+	}
+	else
+		ri_index = InvalidOid;
+
+	relation_mark_replica_identity(partRel, srcrel->rd_rel->relreplident,
+								   ri_index, true, false, AccessExclusiveLock);
+}
+
+/*
  * CloneRowTriggersToPartition
  *		subroutine for ATExecAttachPartition/DefineRelation to create row
  *		triggers on partitions
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4da6719ce7..6145a000cb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3113,7 +3113,8 @@ describeOneTableDetails(const char *schemaname,
 
 		if (verbose &&
 			(tableinfo.relkind == RELKIND_RELATION ||
-			 tableinfo.relkind == RELKIND_MATVIEW) &&
+			 tableinfo.relkind == RELKIND_MATVIEW ||
+			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE) &&
 
 		/*
 		 * No need to display default values; we already display a REPLICA
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 175ecd2879..d6014df840 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -181,3 +181,163 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity_othertable;
+----
+-- Make sure it propagates to partitions
+----
+CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (1000) TO (2000);
+CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1
+  FOR VALUES FROM (1000) TO (1500);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (2000) TO (3000);
+CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part);
+ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4
+  FOR VALUES FROM (3000) TO (4000);
+\d+ test_replica_identity_part2
+                        Table "public.test_replica_identity_part2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part11
+                       Table "public.test_replica_identity_part11"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500)
+Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part
+                  Partitioned table "public.test_replica_identity_part"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition key: RANGE (a)
+Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED,
+            test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000),
+            test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000),
+            test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000)
+Replica Identity: FULL
+
+\d+ test_replica_identity_part3
+                        Table "public.test_replica_identity_part3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part4
+                        Table "public.test_replica_identity_part4"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000))
+Replica Identity: FULL
+
+ALTER TABLE test_replica_identity_part ALTER a SET NOT NULL;
+CREATE UNIQUE INDEX trip_b_idx ON test_replica_identity_part (a);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY USING INDEX trip_b_idx;
+\d+ test_replica_identity_part2
+                        Table "public.test_replica_identity_part2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000))
+Indexes:
+    "test_replica_identity_part2_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+\d+ test_replica_identity_part11
+                       Table "public.test_replica_identity_part11"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500)
+Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500))
+Indexes:
+    "test_replica_identity_part11_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+\d+ test_replica_identity_part
+                  Partitioned table "public.test_replica_identity_part"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition key: RANGE (a)
+Indexes:
+    "trip_b_idx" UNIQUE, btree (a) REPLICA IDENTITY
+Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED,
+            test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000),
+            test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000),
+            test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000)
+
+\d+ test_replica_identity_part3
+                        Table "public.test_replica_identity_part3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000))
+Indexes:
+    "test_replica_identity_part3_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+\d+ test_replica_identity_part4
+                        Table "public.test_replica_identity_part4"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000))
+Indexes:
+    "test_replica_identity_part4_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+----
+-- Check behavior with inherited tables
+----
+CREATE TABLE test_replica_identity_inh (a int);
+CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh);
+ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh);
+\d+ test_replica_identity_inh
+                         Table "public.test_replica_identity_inh"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Child tables: test_replica_identity_cld,
+              test_replica_identity_cld2
+Replica Identity: FULL
+
+\d+ test_replica_identity_cld
+                         Table "public.test_replica_identity_cld"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Inherits: test_replica_identity_inh
+
+\d+ test_replica_identity_cld2
+                        Table "public.test_replica_identity_cld2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Inherits: test_replica_identity_inh
+
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index b08a3623b8..9e309796f2 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -77,3 +77,46 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity_othertable;
+
+----
+-- Make sure it propagates to partitions
+----
+CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (1000) TO (2000);
+CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1
+  FOR VALUES FROM (1000) TO (1500);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (2000) TO (3000);
+CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part);
+ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4
+  FOR VALUES FROM (3000) TO (4000);
+\d+ test_replica_identity_part2
+\d+ test_replica_identity_part11
+\d+ test_replica_identity_part
+\d+ test_replica_identity_part3
+\d+ test_replica_identity_part4
+
+ALTER TABLE test_replica_identity_part ALTER a SET NOT NULL;
+CREATE UNIQUE INDEX trip_b_idx ON test_replica_identity_part (a);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY USING INDEX trip_b_idx;
+\d+ test_replica_identity_part2
+\d+ test_replica_identity_part11
+\d+ test_replica_identity_part
+\d+ test_replica_identity_part3
+\d+ test_replica_identity_part4
+
+
+----
+-- Check behavior with inherited tables
+----
+CREATE TABLE test_replica_identity_inh (a int);
+CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh);
+ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh);
+\d+ test_replica_identity_inh
+\d+ test_replica_identity_cld
+\d+ test_replica_identity_cld2
-- 
2.11.0


--wqciur7sbsf3uhpb--




^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v2 2/2] Propagate replica identity to partitions
@ 2019-02-04 16:43  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Alvaro Herrera @ 2019-02-04 16:43 UTC (permalink / raw)

---
 src/backend/commands/tablecmds.c               | 108 +++++++++++++++--
 src/bin/psql/describe.c                        |   3 +-
 src/test/regress/expected/replica_identity.out | 160 +++++++++++++++++++++++++
 src/test/regress/sql/replica_identity.sql      |  43 +++++++
 4 files changed, 304 insertions(+), 10 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 877bac506f..22cec85ab0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -300,7 +300,7 @@ static void truncate_check_activity(Relation rel);
 static void RangeVarCallbackForTruncate(const RangeVar *relation,
 							Oid relId, Oid oldRelId, void *arg);
 static List *MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supconstr);
+				bool is_partition, List **supconstr, char *ri_type);
 static bool MergeCheckConstraint(List *constraints, char *name, Node *expr);
 static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel);
 static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel);
@@ -485,6 +485,7 @@ static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel);
 static void QueuePartitionConstraintValidation(List **wqueue, Relation scanrel,
 								   List *partConstraint,
 								   bool validate_default);
+static void MatchReplicaIdentity(Relation tgtrel, Relation srcrel);
 static void CloneRowTriggersToPartition(Relation parent, Relation partition);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
 static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
@@ -527,6 +528,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	TupleDesc	descriptor;
 	List	   *inheritOids;
 	List	   *old_constraints;
+	char		ri_type;
 	List	   *rawDefaults;
 	List	   *cookedDefaults;
 	Datum		reloptions;
@@ -708,7 +710,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		MergeAttributes(stmt->tableElts, inheritOids,
 						stmt->relation->relpersistence,
 						stmt->partbound != NULL,
-						&old_constraints);
+						&old_constraints, &ri_type);
 
 	/*
 	 * Create a tuple descriptor from the relation schema.  Note that this
@@ -1014,6 +1016,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		 */
 		CloneForeignKeyConstraints(parentId, relationId, NULL);
 
+		/* Propagate REPLICA IDENTITY information too */
+		if (ri_type != REPLICA_IDENTITY_DEFAULT)
+			MatchReplicaIdentity(rel, parent);
+
 		table_close(parent, NoLock);
 	}
 
@@ -1873,6 +1879,8 @@ storage_name(char c)
  * Output arguments:
  * 'supconstr' receives a list of constraints belonging to the parents,
  *		updated as necessary to be valid for the child.
+ * 'ri_type' receives the replica identity type of the last parent seen,
+ *		or default if none.
  *
  * Return value:
  * Completed schema list.
@@ -1914,11 +1922,15 @@ storage_name(char c)
  *		(4) Otherwise the inherited default is used.
  *		Rule (3) is new in Postgres 7.1; in earlier releases you got a
  *		rather arbitrary choice of which parent default to use.
+ *
+ *	  It only makes sense to use the returned 'ri_type' when there's a single
+ *	  parent, such as in declarative partitioning.
  *----------
  */
 static List *
 MergeAttributes(List *schema, List *supers, char relpersistence,
-				bool is_partition, List **supconstr)
+				bool is_partition, List **supconstr,
+				char *ri_type)
 {
 	ListCell   *entry;
 	List	   *inhSchema = NIL;
@@ -2015,6 +2027,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 	}
 
 	/*
+	 * Initialize replica identity to default; parents may change it later
+	 */
+	*ri_type = REPLICA_IDENTITY_DEFAULT;
+
+	/*
 	 * Scan the parents left-to-right, and merge their attributes to form a
 	 * list of inherited attributes (inhSchema).  Also check to see if we need
 	 * to inherit an OID column.
@@ -2095,6 +2112,9 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 							? "cannot inherit from temporary relation of another session"
 							: "cannot create as partition of temporary relation of another session")));
 
+		/* Indicate replica identity back to caller */
+		*ri_type = relation->rd_rel->relreplident;
+
 		/*
 		 * We should have an UNDER permission flag for this, but for now,
 		 * demand that creator of a child table own the parent.
@@ -3935,7 +3955,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_ReplicaIdentity:	/* REPLICA IDENTITY ... */
 			ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW);
 			pass = AT_PASS_MISC;
-			/* This command never recurses */
+			/* Recursion occurs during execution phase */
 			/* No command-specific prep needed */
 			break;
 		case AT_EnableTrig:		/* ENABLE TRIGGER variants */
@@ -12756,7 +12776,7 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
  */
 static void
 relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
-							   bool is_internal)
+							   bool is_internal, LOCKMODE lockmode)
 {
 	Relation	pg_index;
 	Relation	pg_class;
@@ -12847,6 +12867,42 @@ relation_mark_replica_identity(Relation rel, char ri_type, Oid indexOid,
 	}
 
 	table_close(pg_index, RowExclusiveLock);
+
+	/*
+	 * If there are any partitions, handle them too.
+	 */
+	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+	{
+		PartitionDesc	pd = RelationGetPartitionDesc(rel);
+
+		for (int i = 0; i < pd->nparts; i++)
+		{
+			Relation	part = table_open(pd->oids[i], lockmode);
+			Oid			idxOid;
+
+			if (ri_type == REPLICA_IDENTITY_INDEX)
+			{
+				idxOid = index_get_partition(part, indexOid);
+				if (!OidIsValid(idxOid))
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+							 errmsg("replica index does not exist in partition \"%s\"",
+									RelationGetRelationName(part))));
+
+				LockRelationOid(idxOid, ShareLock);
+			}
+			else
+				idxOid = InvalidOid;
+
+			idxOid = ri_type == REPLICA_IDENTITY_INDEX ?
+				index_get_partition(part, indexOid) : InvalidOid;
+
+			relation_mark_replica_identity(part, ri_type, idxOid, true,
+										   lockmode);
+
+			table_close(part, NoLock);
+		}
+	}
 }
 
 /*
@@ -12861,17 +12917,20 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 
 	if (stmt->identity_type == REPLICA_IDENTITY_DEFAULT)
 	{
-		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true,
+									   lockmode);
 		return;
 	}
 	else if (stmt->identity_type == REPLICA_IDENTITY_FULL)
 	{
-		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true,
+									   lockmode);
 		return;
 	}
 	else if (stmt->identity_type == REPLICA_IDENTITY_NOTHING)
 	{
-		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true);
+		relation_mark_replica_identity(rel, stmt->identity_type, InvalidOid, true,
+									   lockmode);
 		return;
 	}
 	else if (stmt->identity_type == REPLICA_IDENTITY_INDEX)
@@ -12959,7 +13018,8 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode
 	}
 
 	/* This index is suitable for use as a replica identity. Mark it. */
-	relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true);
+	relation_mark_replica_identity(rel, stmt->identity_type, indexOid, true,
+								   lockmode);
 
 	index_close(indexRel, NoLock);
 }
@@ -14664,6 +14724,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
 	/* and triggers */
 	CloneRowTriggersToPartition(rel, attachrel);
 
+	/* Propagate REPLICA IDENTITY information */
+	if (rel->rd_rel->relreplident != REPLICA_IDENTITY_DEFAULT)
+		MatchReplicaIdentity(attachrel, rel);
+
 	/*
 	 * Clone foreign key constraints, and setup for Phase 3 to verify them.
 	 */
@@ -14915,6 +14979,32 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
 }
 
 /*
+ * Set up partRel's (a partition) replica identity to match parentRel's (its
+ * parent).
+ */
+static void
+MatchReplicaIdentity(Relation partRel, Relation srcrel)
+{
+	Oid		ri_index;
+
+	if (srcrel->rd_rel->relreplident == REPLICA_IDENTITY_INDEX)
+	{
+		ri_index = index_get_partition(partRel,
+									   RelationGetReplicaIndex(srcrel));
+		if (!OidIsValid(ri_index))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("replica index does not exist in partition \"%s\"",
+							RelationGetRelationName(partRel))));
+	}
+	else
+		ri_index = InvalidOid;
+
+	relation_mark_replica_identity(partRel, srcrel->rd_rel->relreplident,
+								   ri_index, true, AccessExclusiveLock);
+}
+
+/*
  * CloneRowTriggersToPartition
  *		subroutine for ATExecAttachPartition/DefineRelation to create row
  *		triggers on partitions
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4da6719ce7..6145a000cb 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3113,7 +3113,8 @@ describeOneTableDetails(const char *schemaname,
 
 		if (verbose &&
 			(tableinfo.relkind == RELKIND_RELATION ||
-			 tableinfo.relkind == RELKIND_MATVIEW) &&
+			 tableinfo.relkind == RELKIND_MATVIEW ||
+			 tableinfo.relkind == RELKIND_PARTITIONED_TABLE) &&
 
 		/*
 		 * No need to display default values; we already display a REPLICA
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 175ecd2879..d6014df840 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -181,3 +181,163 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity_othertable;
+----
+-- Make sure it propagates to partitions
+----
+CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (1000) TO (2000);
+CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1
+  FOR VALUES FROM (1000) TO (1500);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (2000) TO (3000);
+CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part);
+ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4
+  FOR VALUES FROM (3000) TO (4000);
+\d+ test_replica_identity_part2
+                        Table "public.test_replica_identity_part2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part11
+                       Table "public.test_replica_identity_part11"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500)
+Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part
+                  Partitioned table "public.test_replica_identity_part"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition key: RANGE (a)
+Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED,
+            test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000),
+            test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000),
+            test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000)
+Replica Identity: FULL
+
+\d+ test_replica_identity_part3
+                        Table "public.test_replica_identity_part3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000))
+Replica Identity: FULL
+
+\d+ test_replica_identity_part4
+                        Table "public.test_replica_identity_part4"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000))
+Replica Identity: FULL
+
+ALTER TABLE test_replica_identity_part ALTER a SET NOT NULL;
+CREATE UNIQUE INDEX trip_b_idx ON test_replica_identity_part (a);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY USING INDEX trip_b_idx;
+\d+ test_replica_identity_part2
+                        Table "public.test_replica_identity_part2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (1000) TO (2000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 1000) AND (a < 2000))
+Indexes:
+    "test_replica_identity_part2_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+\d+ test_replica_identity_part11
+                       Table "public.test_replica_identity_part11"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part1 FOR VALUES FROM (1000) TO (1500)
+Partition constraint: ((a IS NOT NULL) AND (a >= 0) AND (a < 1000) AND (a IS NOT NULL) AND (a >= 1000) AND (a < 1500))
+Indexes:
+    "test_replica_identity_part11_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+\d+ test_replica_identity_part
+                  Partitioned table "public.test_replica_identity_part"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition key: RANGE (a)
+Indexes:
+    "trip_b_idx" UNIQUE, btree (a) REPLICA IDENTITY
+Partitions: test_replica_identity_part1 FOR VALUES FROM (0) TO (1000), PARTITIONED,
+            test_replica_identity_part2 FOR VALUES FROM (1000) TO (2000),
+            test_replica_identity_part3 FOR VALUES FROM (2000) TO (3000),
+            test_replica_identity_part4 FOR VALUES FROM (3000) TO (4000)
+
+\d+ test_replica_identity_part3
+                        Table "public.test_replica_identity_part3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (2000) TO (3000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 2000) AND (a < 3000))
+Indexes:
+    "test_replica_identity_part3_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+\d+ test_replica_identity_part4
+                        Table "public.test_replica_identity_part4"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           | not null |         | plain   |              | 
+ b      | integer |           |          |         | plain   |              | 
+Partition of: test_replica_identity_part FOR VALUES FROM (3000) TO (4000)
+Partition constraint: ((a IS NOT NULL) AND (a >= 3000) AND (a < 4000))
+Indexes:
+    "test_replica_identity_part4_a_idx" UNIQUE, btree (a) REPLICA IDENTITY
+
+----
+-- Check behavior with inherited tables
+----
+CREATE TABLE test_replica_identity_inh (a int);
+CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh);
+ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh);
+\d+ test_replica_identity_inh
+                         Table "public.test_replica_identity_inh"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Child tables: test_replica_identity_cld,
+              test_replica_identity_cld2
+Replica Identity: FULL
+
+\d+ test_replica_identity_cld
+                         Table "public.test_replica_identity_cld"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Inherits: test_replica_identity_inh
+
+\d+ test_replica_identity_cld2
+                        Table "public.test_replica_identity_cld2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Inherits: test_replica_identity_inh
+
diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql
index b08a3623b8..9e309796f2 100644
--- a/src/test/regress/sql/replica_identity.sql
+++ b/src/test/regress/sql/replica_identity.sql
@@ -77,3 +77,46 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 
 DROP TABLE test_replica_identity;
 DROP TABLE test_replica_identity_othertable;
+
+----
+-- Make sure it propagates to partitions
+----
+CREATE TABLE test_replica_identity_part (a int, b int) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part1 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (a);
+CREATE TABLE test_replica_identity_part2 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (1000) TO (2000);
+CREATE TABLE test_replica_identity_part11 PARTITION OF test_replica_identity_part1
+  FOR VALUES FROM (1000) TO (1500);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_part3 PARTITION OF test_replica_identity_part
+  FOR VALUES FROM (2000) TO (3000);
+CREATE TABLE test_replica_identity_part4 (LIKE test_replica_identity_part);
+ALTER TABLE test_replica_identity_part ATTACH PARTITION test_replica_identity_part4
+  FOR VALUES FROM (3000) TO (4000);
+\d+ test_replica_identity_part2
+\d+ test_replica_identity_part11
+\d+ test_replica_identity_part
+\d+ test_replica_identity_part3
+\d+ test_replica_identity_part4
+
+ALTER TABLE test_replica_identity_part ALTER a SET NOT NULL;
+CREATE UNIQUE INDEX trip_b_idx ON test_replica_identity_part (a);
+ALTER TABLE test_replica_identity_part REPLICA IDENTITY USING INDEX trip_b_idx;
+\d+ test_replica_identity_part2
+\d+ test_replica_identity_part11
+\d+ test_replica_identity_part
+\d+ test_replica_identity_part3
+\d+ test_replica_identity_part4
+
+
+----
+-- Check behavior with inherited tables
+----
+CREATE TABLE test_replica_identity_inh (a int);
+CREATE TABLE test_replica_identity_cld () INHERITS (test_replica_identity_inh);
+ALTER TABLE test_replica_identity_inh REPLICA IDENTITY FULL;
+CREATE TABLE test_replica_identity_cld2 () INHERITS (test_replica_identity_inh);
+\d+ test_replica_identity_inh
+\d+ test_replica_identity_cld
+\d+ test_replica_identity_cld2
-- 
2.11.0


--v53z3yvcsi6z6wpg--




^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v9 09/11] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 888560f78e..4ce39516d7 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -616,7 +616,7 @@ Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, Log_directory,
-			FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_DIRS|FLAG_SKIP_HIDDEN|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..dacaafcae6 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--32u276st3Jlj2kUU
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v9-0010-pg_ls_-dir-to-show-directories-and-isdir-column.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v18 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--5I6of5zJg18YgZEa
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v18-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v20 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a4d4782c8c..e1fb160124 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -683,7 +683,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--Z1Z8UV8BNhgCynIS
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v20-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v21 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 92e045e613..16a929f5bb 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -683,7 +683,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--Tcb1KvpfnM4LxW2s
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v21-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v10 8/9] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 3ea8c5683e..19e0b8a82c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -612,7 +612,7 @@ Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
 	return pg_ls_dir_files(fcinfo, Log_directory,
-			FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
+			FLAG_MISSING_OK|FLAG_SKIP_HIDDEN|FLAG_SKIP_SPECIAL|FLAG_METADATA);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..dacaafcae6 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification 
+------+------+--------------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--rCwQ2Y43eQY6RBgR
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v10-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v11 8/9] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 9c2feb0986..2d4f561ae0 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -632,7 +632,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--oTHb8nViIGeoXxdp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v11-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v12 10/11] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index de63e77836..810c6b0f23 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -651,7 +651,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--wIc/V6YLA2QdyfT4
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v12-0011-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v13 7/8] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 342d6f1205..21c265aab3 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -642,7 +642,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_MODERN|LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--E0h0CbphJD8hN+Gf
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v13-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v14 7/8] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 21611dfbc0..00de0c091d 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -653,7 +653,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--Kynn+LdAwU9N+JqL
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v14-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v37 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 5356136ff5a..5402ecb3d05 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -711,7 +711,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index ce5d6f73e01..53662fb0efd 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -41,6 +41,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index cf683c3bf3a..18b435c9abb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -32,6 +32,10 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.25.1


--Pk/CTwBz1VvfPIDp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v37-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v36 6/7] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fd837e22987..a01c7109f4c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -641,7 +641,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index ce5d6f73e01..53662fb0efd 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -41,6 +41,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index cf683c3bf3a..18b435c9abb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -32,6 +32,10 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1


--4ybNbZnZ8tziJ7D6
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v36-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v34 06/15] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1c84be69e91..80516833e33 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -639,7 +639,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 20a7245d5e1..50a3a13fbea 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -32,6 +32,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 3aadac8b611..034c903b41c 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -28,6 +28,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1


--smOfPzt+Qjm5bNGJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v34-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v35 6/7] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index cfb7fc7e080..ca5223be2ee 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -641,7 +641,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index ce5d6f73e01..53662fb0efd 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -41,6 +41,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index cf683c3bf3a..18b435c9abb 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -32,6 +32,10 @@ SELECT regexp_replace(pg_tablespace_location(oid), '(pg_tblspc)/(\d+)', '\1/NNN'
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1


--olLTNZSltDMg5Vbm
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v35-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v32 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index eed71892bd1..c381a170ad6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 7d8398b0ca1..549117256fc 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -24,6 +24,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 291ca029c14..cc5f2f9d0dc 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -21,6 +21,10 @@ CREATE TABLESPACE regress_tblspace LOCATION :'testtablespace';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--Bne5rrxQd65beI7a
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v32-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v31 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index eed71892bd..c381a170ad 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index e69fa17004..da624020b2 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index c146a4c129..3689bfac6d 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--qZVVwWJgpX9Jzs7f
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v31-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v33 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c          | 2 +-
 src/test/regress/expected/tablespace.out | 7 +++++++
 src/test/regress/sql/tablespace.sql      | 4 ++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index e24c43e3a9b..e49a15aa279 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -681,7 +681,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/expected/tablespace.out b/src/test/regress/expected/tablespace.out
index 20a7245d5e1..50a3a13fbea 100644
--- a/src/test/regress/expected/tablespace.out
+++ b/src/test/regress/expected/tablespace.out
@@ -32,6 +32,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/sql/tablespace.sql b/src/test/regress/sql/tablespace.sql
index 3aadac8b611..034c903b41c 100644
--- a/src/test/regress/sql/tablespace.sql
+++ b/src/test/regress/sql/tablespace.sql
@@ -28,6 +28,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.1


--9CzcV6dAFIr7O1Ie
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v33-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v30 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 12bb70c442..f3437d306a 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -679,7 +679,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a32212be04..bb184d3fe0 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 4183a77b23..3571f14626 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--ZwgA9U+XZDXt4+m+
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v30-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v25 07/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d21a95aebe..07fce2a9bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--SBikYMzjhZGK9d4p
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v25-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v26 07/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d21a95aebe..07fce2a9bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--yKkOmjQZXRsvHRX8
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v26-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v27 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a7d1a65f10..e25010d40c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a32212be04..bb184d3fe0 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 4183a77b23..3571f14626 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--19uQFt6ulqmgNgg1
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v27-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v28 06/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a7d1a65f10..e25010d40c 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -680,7 +680,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index a32212be04..bb184d3fe0 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 4183a77b23..3571f14626 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--vk/v8fjDPiDepTtA
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v28-0007-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v22 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a61ddfc451..35fe4d7cc1 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--d6Gm4EdcadzBjdND
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v22-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v23 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index a07d1717f9..f7a0cb3f46 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--mhjHhnbe5PrRcwjY
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v23-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v24 07/11] pg_ls_logdir to ignore error if initial/top dir is missing..
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

..since ./log is created dynamically and not by initdb
---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index d21a95aebe..07fce2a9bc 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -677,7 +677,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--mPTHnM80CEnHQ2WJ
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v24-0008-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v19 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--S0GG+JvAI2G0KxBG
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v19-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--2FkSFaIQeDFoAt0B
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v16-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing
@ 2020-03-06 23:23  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Justin Pryzby @ 2020-03-06 23:23 UTC (permalink / raw)

---
 src/backend/utils/adt/genfile.c           | 2 +-
 src/test/regress/input/tablespace.source  | 4 ++++
 src/test/regress/output/tablespace.source | 7 +++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fba63568b6..91bf8c69e9 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -647,7 +647,7 @@ pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, int flags)
 Datum
 pg_ls_logdir(PG_FUNCTION_ARGS)
 {
-	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON);
+	return pg_ls_dir_files(fcinfo, Log_directory, LS_DIR_COMMON | LS_DIR_MISSING_OK);
 }
 
 /* Function to return the list of files in the WAL directory */
diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source
index 0b9cfe615e..2a1268e17c 100644
--- a/src/test/regress/input/tablespace.source
+++ b/src/test/regress/input/tablespace.source
@@ -16,6 +16,10 @@ CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@';
 -- The query is written to ERROR if the tablespace doesn't exist, rather than silently failing to call pg_ls_tmpdir()
 SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspace' UNION SELECT 0 ORDER BY 1 DESC LIMIT 1) AS b , pg_ls_tmpdir(oid) AS c WHERE c.name='Does not exist';
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source
index 1c88e914e3..ba9a3fe29a 100644
--- a/src/test/regress/output/tablespace.source
+++ b/src/test/regress/output/tablespace.source
@@ -21,6 +21,13 @@ SELECT c.* FROM (SELECT oid FROM pg_tablespace b WHERE b.spcname='regress_tblspa
 ------+------+--------------+-------
 (0 rows)
 
+-- This tests the missing_ok parameter.  If that's not functioning, this would ERROR if the logdir doesn't exist yet.
+-- The name='' condition is never true, so the function runs to completion but returns zero rows.
+SELECT * FROM pg_ls_logdir() WHERE name='Does not exist';
+ name | size | modification | isdir 
+------+------+--------------+-------
+(0 rows)
+
 -- try setting and resetting some properties for the new tablespace
 ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1);
 ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true);  -- fail
-- 
2.17.0


--4LFBTxd4L5NLO6ly
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
 filename="v17-0009-pg_ls_-dir-to-return-all-the-metadata-from-pg_st.patch"



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Using defines for protocol characters
@ 2023-08-03 17:40  Dave Cramer <[email protected]>
  0 siblings, 2 replies; 80+ messages in thread

From: Dave Cramer @ 2023-08-03 17:40 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>; +Cc: [email protected]

Greetings,

Attached is a patch which introduces a file protocol.h. Instead of using
the actual characters everywhere in the code this patch names the
characters and removes the comments beside each usage.


Dave Cramer


Attachments:

  [application/octet-stream] 0001-Created-protocol.h.patch (49.0K, ../../CADK3HHKbBmK-PKf1bPNFoMC+oBt+pD9PH8h5nvmBQskEHm-Ehw@mail.gmail.com/3-0001-Created-protocol.h.patch)
  download | inline diff:
From af8950d6d40d707860fee89c86e9d0730d77bcc1 Mon Sep 17 00:00:00 2001
From: Dave Cramer <[email protected]>
Date: Thu, 20 Apr 2023 15:40:03 -0400
Subject: [PATCH] Created protocol.h Protocol.h has defines for every protocol
 message both backend and frontend Instead of using hardcoded values for each
 protocol message use defines to make code easier to read

remove redundant comments

fix rebase errors
---
 src/backend/access/common/printsimple.c |  5 +-
 src/backend/access/transam/parallel.c   | 17 +++---
 src/backend/backup/basebackup_copy.c    | 17 +++---
 src/backend/commands/async.c            |  3 +-
 src/backend/commands/copyfromparse.c    | 23 ++++----
 src/backend/commands/copyto.c           |  5 +-
 src/backend/libpq/auth-sasl.c           |  3 +-
 src/backend/libpq/auth.c                |  9 +--
 src/backend/postmaster/postmaster.c     |  3 +-
 src/backend/replication/walsender.c     | 19 +++---
 src/backend/tcop/dest.c                 |  5 +-
 src/backend/tcop/fastpath.c             |  3 +-
 src/backend/tcop/postgres.c             | 77 +++++++++++++------------
 src/backend/utils/error/elog.c          |  3 +-
 src/backend/utils/misc/guc.c            |  3 +-
 src/include/protocol.h                  | 60 +++++++++++++++++++
 src/interfaces/libpq/fe-auth.c          |  3 +-
 src/interfaces/libpq/fe-connect.c       | 15 ++---
 src/interfaces/libpq/fe-exec.c          | 39 +++++++------
 src/interfaces/libpq/fe-protocol3.c     | 56 +++++++++---------
 src/interfaces/libpq/fe-trace.c         | 61 ++++++++++----------
 21 files changed, 255 insertions(+), 174 deletions(-)
 create mode 100644 src/include/protocol.h

diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index ef818228ac..5e9ecc83b9 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -21,6 +21,7 @@
 #include "access/printsimple.h"
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
+#include "protocol.h"
 #include "utils/builtins.h"
 
 /*
@@ -32,7 +33,7 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc)
 	StringInfoData buf;
 	int			i;
 
-	pq_beginmessage(&buf, 'T'); /* RowDescription */
+	pq_beginmessage(&buf, ROW_DESCRIPTION_RESPONSE);
 	pq_sendint16(&buf, tupdesc->natts);
 
 	for (i = 0; i < tupdesc->natts; ++i)
@@ -65,7 +66,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self)
 	slot_getallattrs(slot);
 
 	/* Prepare and send message */
-	pq_beginmessage(&buf, 'D');
+	pq_beginmessage(&buf, DATA_ROW_RESPONSE);
 	pq_sendint16(&buf, tupdesc->natts);
 
 	for (i = 0; i < tupdesc->natts; ++i)
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 1738aecf1f..fd94ce2006 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -33,6 +33,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "protocol.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/sinval.h"
@@ -1127,7 +1128,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 
 	switch (msgtype)
 	{
-		case 'K':				/* BackendKeyData */
+		case BACKEND_KEY_DATA:
 			{
 				int32		pid = pq_getmsgint(msg, 4);
 
@@ -1137,8 +1138,8 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'E':				/* ErrorResponse */
-		case 'N':				/* NoticeResponse */
+		case ERROR_RESPONSE:
+		case NOTICE_RESPONSE:
 			{
 				ErrorData	edata;
 				ErrorContextCallback *save_error_context_stack;
@@ -1183,7 +1184,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'A':				/* NotifyResponse */
+		case NOTIFY_RESPONSE:
 			{
 				/* Propagate NotifyResponse. */
 				int32		pid;
@@ -1200,7 +1201,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'P':				/* Parallel progress reporting */
+		case PARALLEL_PROGRESS_RESPONSE:
 			{
 				/*
 				 * Only incremental progress reporting is currently supported.
@@ -1217,7 +1218,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'X':				/* Terminate, indicating clean exit */
+		case TERMINATE_REQUEST:	/* Terminate, indicating clean exit */
 			{
 				shm_mq_detach(pcxt->worker[i].error_mqh);
 				pcxt->worker[i].error_mqh = NULL;
@@ -1372,7 +1373,7 @@ ParallelWorkerMain(Datum main_arg)
 	 * protocol message is defined, but it won't actually be used for anything
 	 * in this case.
 	 */
-	pq_beginmessage(&msgbuf, 'K');
+	pq_beginmessage(&msgbuf, BACKEND_KEY_DATA);
 	pq_sendint32(&msgbuf, (int32) MyProcPid);
 	pq_sendint32(&msgbuf, (int32) MyCancelKey);
 	pq_endmessage(&msgbuf);
@@ -1550,7 +1551,7 @@ ParallelWorkerMain(Datum main_arg)
 	DetachSession();
 
 	/* Report success. */
-	pq_putmessage('X', NULL, 0);
+	pq_putmessage(TERMINATE_REQUEST, NULL, 0);
 }
 
 /*
diff --git a/src/backend/backup/basebackup_copy.c b/src/backend/backup/basebackup_copy.c
index 1db80cde1b..9c2c174555 100644
--- a/src/backend/backup/basebackup_copy.c
+++ b/src/backend/backup/basebackup_copy.c
@@ -32,6 +32,7 @@
 #include "executor/executor.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
+#include "protocol.h"
 #include "tcop/dest.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
@@ -169,7 +170,7 @@ bbsink_copystream_begin_archive(bbsink *sink, const char *archive_name)
 	StringInfoData buf;
 
 	ti = list_nth(state->tablespaces, state->tablespace_num);
-	pq_beginmessage(&buf, 'd'); /* CopyData */
+	pq_beginmessage(&buf, COPY_DATA);
 	pq_sendbyte(&buf, 'n');		/* New archive */
 	pq_sendstring(&buf, archive_name);
 	pq_sendstring(&buf, ti->path == NULL ? "" : ti->path);
@@ -220,8 +221,8 @@ bbsink_copystream_archive_contents(bbsink *sink, size_t len)
 		{
 			mysink->last_progress_report_time = now;
 
-			pq_beginmessage(&buf, 'd'); /* CopyData */
-			pq_sendbyte(&buf, 'p'); /* Progress report */
+			pq_beginmessage(&buf, COPY_DATA);
+			pq_sendbyte(&buf, COPY_PROGRESS);
 			pq_sendint64(&buf, state->bytes_done);
 			pq_endmessage(&buf);
 			pq_flush_if_writable();
@@ -246,8 +247,8 @@ bbsink_copystream_end_archive(bbsink *sink)
 
 	mysink->bytes_done_at_last_time_check = state->bytes_done;
 	mysink->last_progress_report_time = GetCurrentTimestamp();
-	pq_beginmessage(&buf, 'd'); /* CopyData */
-	pq_sendbyte(&buf, 'p');		/* Progress report */
+	pq_beginmessage(&buf, COPY_DATA);
+	pq_sendbyte(&buf, COPY_PROGRESS);
 	pq_sendint64(&buf, state->bytes_done);
 	pq_endmessage(&buf);
 	pq_flush_if_writable();
@@ -261,7 +262,7 @@ bbsink_copystream_begin_manifest(bbsink *sink)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf, 'd'); /* CopyData */
+	pq_beginmessage(&buf, COPY_DATA);
 	pq_sendbyte(&buf, 'm');		/* Manifest */
 	pq_endmessage(&buf);
 }
@@ -318,7 +319,7 @@ SendCopyOutResponse(void)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf, 'H');
+	pq_beginmessage(&buf, COPY_OUT_RESPONSE);
 	pq_sendbyte(&buf, 0);		/* overall format */
 	pq_sendint16(&buf, 0);		/* natts */
 	pq_endmessage(&buf);
@@ -330,7 +331,7 @@ SendCopyOutResponse(void)
 static void
 SendCopyDone(void)
 {
-	pq_putemptymessage('c');
+	pq_putemptymessage(COPY_DONE);
 }
 
 /*
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index ef909cf4e0..cd703bcf09 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -141,6 +141,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "protocol.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -2281,7 +2282,7 @@ NotifyMyFrontEnd(const char *channel, const char *payload, int32 srcPid)
 	{
 		StringInfoData buf;
 
-		pq_beginmessage(&buf, 'A');
+		pq_beginmessage(&buf, NOTIFY_RESPONSE);
 		pq_sendint32(&buf, srcPid);
 		pq_sendstring(&buf, channel);
 		pq_sendstring(&buf, payload);
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 232768a6e1..07a60692ef 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -72,6 +72,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -174,7 +175,7 @@ ReceiveCopyBegin(CopyFromState cstate)
 	int16		format = (cstate->opts.binary ? 1 : 0);
 	int			i;
 
-	pq_beginmessage(&buf, 'G');
+	pq_beginmessage(&buf, COPY_IN_RESPONSE);
 	pq_sendbyte(&buf, format);	/* overall format */
 	pq_sendint16(&buf, natts);
 	for (i = 0; i < natts; i++)
@@ -279,13 +280,13 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 					/* Validate message type and set packet size limit */
 					switch (mtype)
 					{
-						case 'd':	/* CopyData */
+						case COPY_DATA:
 							maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 							break;
-						case 'c':	/* CopyDone */
-						case 'f':	/* CopyFail */
-						case 'H':	/* Flush */
-						case 'S':	/* Sync */
+						case COPY_DONE:
+						case COPY_FAIL:
+						case FLUSH_DATA_REQUEST:
+						case SYNC_DATA_REQUEST:
 							maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 							break;
 						default:
@@ -305,20 +306,20 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 					/* ... and process it */
 					switch (mtype)
 					{
-						case 'd':	/* CopyData */
+						case COPY_DATA:
 							break;
-						case 'c':	/* CopyDone */
+						case COPY_DONE:
 							/* COPY IN correctly terminated by frontend */
 							cstate->raw_reached_eof = true;
 							return bytesread;
-						case 'f':	/* CopyFail */
+						case COPY_FAIL:
 							ereport(ERROR,
 									(errcode(ERRCODE_QUERY_CANCELED),
 									 errmsg("COPY from stdin failed: %s",
 											pq_getmsgstring(cstate->fe_msgbuf))));
 							break;
-						case 'H':	/* Flush */
-						case 'S':	/* Sync */
+						case FLUSH_DATA_REQUEST:
+						case SYNC_DATA_REQUEST:
 
 							/*
 							 * Ignore Flush/Sync for the convenience of client
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 9e4b2437a5..4eaa5b9ec3 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -34,6 +34,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "protocol.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
@@ -144,7 +145,7 @@ SendCopyBegin(CopyToState cstate)
 	int16		format = (cstate->opts.binary ? 1 : 0);
 	int			i;
 
-	pq_beginmessage(&buf, 'H');
+	pq_beginmessage(&buf, COPY_OUT_RESPONSE);
 	pq_sendbyte(&buf, format);	/* overall format */
 	pq_sendint16(&buf, natts);
 	for (i = 0; i < natts; i++)
@@ -159,7 +160,7 @@ SendCopyEnd(CopyToState cstate)
 	/* Shouldn't have any unsent data */
 	Assert(cstate->fe_msgbuf->len == 0);
 	/* Send Copy Done message */
-	pq_putemptymessage('c');
+	pq_putemptymessage(COPY_DONE);
 }
 
 /*----------
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 684680897b..9477331e27 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -19,6 +19,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/sasl.h"
+#include "protocol.h"
 
 /*
  * Maximum accepted size of SASL messages.
@@ -87,7 +88,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
 	{
 		pq_startmsgread();
 		mtype = pq_getbyte();
-		if (mtype != 'p')
+		if (mtype != GSS_RESPONSE)
 		{
 			/* Only log error if client didn't disconnect. */
 			if (mtype != EOF)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 315a24bb3f..e6b2561387 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -34,6 +34,7 @@
 #include "libpq/scram.h"
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 #include "postmaster/postmaster.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -665,7 +666,7 @@ sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extrale
 
 	CHECK_FOR_INTERRUPTS();
 
-	pq_beginmessage(&buf, 'R');
+	pq_beginmessage(&buf, AUTHENTICATION_REQUEST);
 	pq_sendint32(&buf, (int32) areq);
 	if (extralen > 0)
 		pq_sendbytes(&buf, extradata, extralen);
@@ -698,7 +699,7 @@ recv_password_packet(Port *port)
 
 	/* Expect 'p' message type */
 	mtype = pq_getbyte();
-	if (mtype != 'p')
+	if (mtype != PASSWORD_RESPONSE)
 	{
 		/*
 		 * If the client just disconnects without offering a password, don't
@@ -961,7 +962,7 @@ pg_GSS_recvauth(Port *port)
 		CHECK_FOR_INTERRUPTS();
 
 		mtype = pq_getbyte();
-		if (mtype != 'p')
+		if (mtype != GSS_RESPONSE)
 		{
 			/* Only log error if client didn't disconnect. */
 			if (mtype != EOF)
@@ -1232,7 +1233,7 @@ pg_SSPI_recvauth(Port *port)
 	{
 		pq_startmsgread();
 		mtype = pq_getbyte();
-		if (mtype != 'p')
+		if (mtype != GSS_RESPONSE)
 		{
 			if (sspictx != NULL)
 			{
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9c8ec779f9..e8a5cb228e 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -114,6 +114,7 @@
 #include "postmaster/pgarch.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
+#include "protocol.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
@@ -2357,7 +2358,7 @@ SendNegotiateProtocolVersion(List *unrecognized_protocol_options)
 	StringInfoData buf;
 	ListCell   *lc;
 
-	pq_beginmessage(&buf, 'v'); /* NegotiateProtocolVersion */
+	pq_beginmessage(&buf, NEGOTIATE_PROTOCOL);
 	pq_sendint32(&buf, PG_PROTOCOL_LATEST);
 	pq_sendint32(&buf, list_length(unrecognized_protocol_options));
 	foreach(lc, unrecognized_protocol_options)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d27ef2985d..419594f4a3 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -69,6 +69,7 @@
 #include "nodes/replnodes.h"
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
+#include "protocol.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
@@ -603,7 +604,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd)
 	dest->rStartup(dest, CMD_SELECT, tupdesc);
 
 	/* Send a DataRow message */
-	pq_beginmessage(&buf, 'D');
+	pq_beginmessage(&buf, DATA_ROW_RESPONSE);
 	pq_sendint16(&buf, 2);		/* # of columns */
 	len = strlen(histfname);
 	pq_sendint32(&buf, len);	/* col1 len */
@@ -801,7 +802,7 @@ StartReplication(StartReplicationCmd *cmd)
 		WalSndSetState(WALSNDSTATE_CATCHUP);
 
 		/* Send a CopyBothResponse message, and start streaming */
-		pq_beginmessage(&buf, 'W');
+		pq_beginmessage(&buf, COPY_BOTH_RESPONSE);
 		pq_sendbyte(&buf, 0);
 		pq_sendint16(&buf, 0);
 		pq_endmessage(&buf);
@@ -1294,7 +1295,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	WalSndSetState(WALSNDSTATE_CATCHUP);
 
 	/* Send a CopyBothResponse message, and start streaming */
-	pq_beginmessage(&buf, 'W');
+	pq_beginmessage(&buf, COPY_BOTH_RESPONSE);
 	pq_sendbyte(&buf, 0);
 	pq_sendint16(&buf, 0);
 	pq_endmessage(&buf);
@@ -1923,11 +1924,11 @@ ProcessRepliesIfAny(void)
 		/* Validate message type and set packet size limit */
 		switch (firstchar)
 		{
-			case 'd':
+			case COPY_DATA:
 				maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 				break;
-			case 'c':
-			case 'X':
+			case COPY_DONE:
+			case TERMINATE_REQUEST:
 				maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 				break;
 			default:
@@ -1955,7 +1956,7 @@ ProcessRepliesIfAny(void)
 				/*
 				 * 'd' means a standby reply wrapped in a CopyData packet.
 				 */
-			case 'd':
+			case COPY_DATA:
 				ProcessStandbyMessage();
 				received = true;
 				break;
@@ -1964,7 +1965,7 @@ ProcessRepliesIfAny(void)
 				 * CopyDone means the standby requested to finish streaming.
 				 * Reply with CopyDone, if we had not sent that already.
 				 */
-			case 'c':
+			case COPY_DONE:
 				if (!streamingDoneSending)
 				{
 					pq_putmessage_noblock('c', NULL, 0);
@@ -1978,7 +1979,7 @@ ProcessRepliesIfAny(void)
 				/*
 				 * 'X' means that the standby is closing down the socket.
 				 */
-			case 'X':
+			case TERMINATE_REQUEST:
 				proc_exit(0);
 
 			default:
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index c0406e2ee5..4b5e2e12e5 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -39,6 +39,7 @@
 #include "executor/tstoreReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
+#include "protocol.h"
 #include "utils/portal.h"
 
 
@@ -220,7 +221,7 @@ NullCommand(CommandDest dest)
 		case DestRemoteSimple:
 
 			/* Tell the FE that we saw an empty query string */
-			pq_putemptymessage('I');
+			pq_putemptymessage(EMPTY_QUERY_RESPONSE);
 			break;
 
 		case DestNone:
@@ -258,7 +259,7 @@ ReadyForQuery(CommandDest dest)
 			{
 				StringInfoData buf;
 
-				pq_beginmessage(&buf, 'Z');
+				pq_beginmessage(&buf, READY_FOR_QUERY);
 				pq_sendbyte(&buf, TransactionBlockStatusCode());
 				pq_endmessage(&buf);
 			}
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 2f70ebd5fa..83320cd3f7 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -27,6 +27,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 #include "tcop/fastpath.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
@@ -69,7 +70,7 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf, 'V');
+	pq_beginmessage(&buf, FUNCTION_CALL_RESPONSE);
 
 	if (isnull)
 	{
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 36cc99ec9c..cf9643e088 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -55,6 +55,7 @@
 #include "postmaster/autovacuum.h"
 #include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
+#include "protocol.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
@@ -402,37 +403,37 @@ SocketBackend(StringInfo inBuf)
 	 */
 	switch (qtype)
 	{
-		case 'Q':				/* simple query */
+		case SIMPLE_QUERY:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
 
-		case 'F':				/* fastpath function call */
+		case FUNCTION_CALL_REQUEST:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
 
-		case 'X':				/* terminate */
+		case TERMINATE_REQUEST:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			ignore_till_sync = false;
 			break;
 
-		case 'B':				/* bind */
-		case 'P':				/* parse */
+		case BIND_REQUEST :
+		case PARSE_REQUEST:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = true;
 			break;
 
-		case 'C':				/* close */
-		case 'D':				/* describe */
-		case 'E':				/* execute */
-		case 'H':				/* flush */
+		case CLOSE_REQUEST:
+		case DESCRIBE_REQUEST:
+		case EXECUTE_REQUEST:
+		case FLUSH_DATA_REQUEST:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			doing_extended_query_message = true;
 			break;
 
-		case 'S':				/* sync */
+		case SYNC_DATA_REQUEST:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			/* stop any active skip-till-Sync */
 			ignore_till_sync = false;
@@ -440,13 +441,13 @@ SocketBackend(StringInfo inBuf)
 			doing_extended_query_message = false;
 			break;
 
-		case 'd':				/* copy data */
+		case COPY_DATA:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
 
-		case 'c':				/* copy done */
-		case 'f':				/* copy fail */
+		case COPY_DONE:
+		case COPY_FAIL:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
@@ -1589,7 +1590,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 	 * Send ParseComplete.
 	 */
 	if (whereToSendOutput == DestRemote)
-		pq_putemptymessage('1');
+		pq_putemptymessage(PARSE_COMPLETE_RESPONSE);
 
 	/*
 	 * Emit duration logging if appropriate.
@@ -2047,7 +2048,7 @@ exec_bind_message(StringInfo input_message)
 	 * Send BindComplete.
 	 */
 	if (whereToSendOutput == DestRemote)
-		pq_putemptymessage('2');
+		pq_putemptymessage(BIND_COMPLETE_RESPONSE);
 
 	/*
 	 * Emit duration logging if appropriate.
@@ -2290,7 +2291,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 	{
 		/* Portal run not complete, so send PortalSuspended */
 		if (whereToSendOutput == DestRemote)
-			pq_putemptymessage('s');
+			pq_putemptymessage(PORTAL_SUSPENDED_RESPONSE);
 
 		/*
 		 * Set XACT_FLAGS_PIPELINING whenever we suspend an Execute message,
@@ -2683,7 +2684,7 @@ exec_describe_statement_message(const char *stmt_name)
 								  NULL);
 	}
 	else
-		pq_putemptymessage('n');	/* NoData */
+		pq_putemptymessage(NO_DATA_RESPONSE);
 }
 
 /*
@@ -2736,7 +2737,7 @@ exec_describe_portal_message(const char *portal_name)
 								  FetchPortalTargetList(portal),
 								  portal->formats);
 	else
-		pq_putemptymessage('n');	/* NoData */
+		pq_putemptymessage(NO_DATA_RESPONSE);
 }
 
 
@@ -4239,7 +4240,7 @@ PostgresMain(const char *dbname, const char *username)
 	{
 		StringInfoData buf;
 
-		pq_beginmessage(&buf, 'K');
+		pq_beginmessage(&buf, BACKEND_KEY_DATA);
 		pq_sendint32(&buf, (int32) MyProcPid);
 		pq_sendint32(&buf, (int32) MyCancelKey);
 		pq_endmessage(&buf);
@@ -4618,7 +4619,7 @@ PostgresMain(const char *dbname, const char *username)
 
 		switch (firstchar)
 		{
-			case 'Q':			/* simple query */
+			case SIMPLE_QUERY:
 				{
 					const char *query_string;
 
@@ -4642,7 +4643,7 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'P':			/* parse */
+			case PARSE_REQUEST:
 				{
 					const char *stmt_name;
 					const char *query_string;
@@ -4672,7 +4673,7 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'B':			/* bind */
+			case BIND_REQUEST:
 				forbidden_in_wal_sender(firstchar);
 
 				/* Set statement_timestamp() */
@@ -4687,7 +4688,7 @@ PostgresMain(const char *dbname, const char *username)
 				/* exec_bind_message does valgrind_report_error_query */
 				break;
 
-			case 'E':			/* execute */
+			case EXECUTE_REQUEST:
 				{
 					const char *portal_name;
 					int			max_rows;
@@ -4707,7 +4708,7 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'F':			/* fastpath function call */
+			case FUNCTION_CALL_REQUEST:
 				forbidden_in_wal_sender(firstchar);
 
 				/* Set statement_timestamp() */
@@ -4742,7 +4743,7 @@ PostgresMain(const char *dbname, const char *username)
 				send_ready_for_query = true;
 				break;
 
-			case 'C':			/* close */
+			case CLOSE_REQUEST:
 				{
 					int			close_type;
 					const char *close_target;
@@ -4755,7 +4756,7 @@ PostgresMain(const char *dbname, const char *username)
 
 					switch (close_type)
 					{
-						case 'S':
+						case DESCRIBE_PREPARED:
 							if (close_target[0] != '\0')
 								DropPreparedStatement(close_target, false);
 							else
@@ -4764,7 +4765,7 @@ PostgresMain(const char *dbname, const char *username)
 								drop_unnamed_stmt();
 							}
 							break;
-						case 'P':
+						case DESCRIBE_PORTAL:
 							{
 								Portal		portal;
 
@@ -4782,13 +4783,13 @@ PostgresMain(const char *dbname, const char *username)
 					}
 
 					if (whereToSendOutput == DestRemote)
-						pq_putemptymessage('3');	/* CloseComplete */
+						pq_putemptymessage(CLOSE_COMPLETE_RESPONSE);
 
 					valgrind_report_error_query("CLOSE message");
 				}
 				break;
 
-			case 'D':			/* describe */
+			case DESCRIBE_REQUEST:
 				{
 					int			describe_type;
 					const char *describe_target;
@@ -4804,10 +4805,10 @@ PostgresMain(const char *dbname, const char *username)
 
 					switch (describe_type)
 					{
-						case 'S':
+						case DESCRIBE_PREPARED:
 							exec_describe_statement_message(describe_target);
 							break;
-						case 'P':
+						case DESCRIBE_PORTAL:
 							exec_describe_portal_message(describe_target);
 							break;
 						default:
@@ -4822,13 +4823,13 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'H':			/* flush */
+			case FLUSH_DATA_REQUEST:
 				pq_getmsgend(&input_message);
 				if (whereToSendOutput == DestRemote)
 					pq_flush();
 				break;
 
-			case 'S':			/* sync */
+			case SYNC_DATA_REQUEST:
 				pq_getmsgend(&input_message);
 				finish_xact_command();
 				valgrind_report_error_query("SYNC message");
@@ -4847,7 +4848,7 @@ PostgresMain(const char *dbname, const char *username)
 
 				/* FALLTHROUGH */
 
-			case 'X':
+			case TERMINATE_REQUEST:
 
 				/*
 				 * Reset whereToSendOutput to prevent ereport from attempting
@@ -4865,9 +4866,9 @@ PostgresMain(const char *dbname, const char *username)
 				 */
 				proc_exit(0);
 
-			case 'd':			/* copy data */
-			case 'c':			/* copy done */
-			case 'f':			/* copy fail */
+			case COPY_DATA:
+			case COPY_DONE:
+			case COPY_FAIL:
 
 				/*
 				 * Accept but ignore these messages, per protocol spec; we
@@ -4897,7 +4898,7 @@ forbidden_in_wal_sender(char firstchar)
 {
 	if (am_walsender)
 	{
-		if (firstchar == 'F')
+		if (firstchar == FUNCTION_CALL_REQUEST)
 			ereport(ERROR,
 					(errcode(ERRCODE_PROTOCOL_VIOLATION),
 					 errmsg("fastpath function calls not supported in a replication connection")));
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 5898100acb..17c75479b6 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -77,6 +77,7 @@
 #include "postmaster/bgworker.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
+#include "protocol.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
@@ -3465,7 +3466,7 @@ send_message_to_frontend(ErrorData *edata)
 		char		tbuf[12];
 
 		/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
-		pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
+		pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? NOTICE_RESPONSE : ERROR_RESPONSE);
 
 		sev = error_severity(edata->elevel);
 		pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5308896c87..cfefe9b698 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "libpq/pqformat.h"
 #include "parser/scansup.h"
 #include "port/pg_bitutils.h"
+#include "protocol.h"
 #include "storage/fd.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
@@ -2593,7 +2594,7 @@ ReportGUCOption(struct config_generic *record)
 	{
 		StringInfoData msgbuf;
 
-		pq_beginmessage(&msgbuf, 'S');
+		pq_beginmessage(&msgbuf, PARAMETER_STATUS_RESPONSE);
 		pq_sendstring(&msgbuf, record->name);
 		pq_sendstring(&msgbuf, val);
 		pq_endmessage(&msgbuf);
diff --git a/src/include/protocol.h b/src/include/protocol.h
new file mode 100644
index 0000000000..1d1e1f4034
--- /dev/null
+++ b/src/include/protocol.h
@@ -0,0 +1,60 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol.h
+ *	  Exports from postmaster/postmaster.c.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * src/include/protocol.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _PROTOCOL_H
+#define _PROTOCOL_H
+
+#define BIND_REQUEST                'B'
+#define CLOSE_REQUEST               'C'
+#define DESCRIBE_REQUEST            'D'
+#define EXECUTE_REQUEST             'E'
+#define FUNCTION_CALL_REQUEST       'F'
+#define FLUSH_DATA_REQUEST          'H'
+#define BACKEND_KEY_DATA            'K'
+#define PARSE_REQUEST               'P'
+#define AUTHENTICATION_REQUEST      'R'
+#define SYNC_DATA_REQUEST           'S'
+#define SIMPLE_QUERY                'Q'
+#define TERMINATE_REQUEST           'X'
+#define COPY_FAIL                   'f'
+#define COPY_DONE                   'c'
+#define COPY_DATA                   'd'
+#define COPY_PROGRESS               'p'
+#define DESCRIBE_PREPARED           'S'
+#define DESCRIBE_PORTAL             'P'
+
+/*
+Responses
+*/
+#define PARSE_COMPLETE_RESPONSE '1'
+#define BIND_COMPLETE_RESPONSE  '2'
+#define CLOSE_COMPLETE_RESPONSE '3'
+#define NOTIFY_RESPONSE         'A'
+#define COMMAND_COMPLETE        'C'
+#define DATA_ROW_RESPONSE       'D'
+#define ERROR_RESPONSE          'E'
+#define COPY_IN_RESPONSE        'G'
+#define COPY_OUT_RESPONSE       'H'
+#define EMPTY_QUERY_RESPONSE    'I'
+#define NOTICE_RESPONSE         'N'
+#define PARALLEL_PROGRESS_RESPONSE 'P'
+#define FUNCTION_CALL_RESPONSE  'V'
+#define PARAMETER_STATUS_RESPONSE 'S'
+#define ROW_DESCRIPTION_RESPONSE 'T'
+#define COPY_BOTH_RESPONSE      'W'
+#define READY_FOR_QUERY         'Z'
+#define NO_DATA_RESPONSE        'n'
+#define PASSWORD_RESPONSE       'p'
+#define GSS_RESPONSE            'p'
+#define PORTAL_SUSPENDED_RESPONSE 's'
+#define PARAMETER_DESCRIPTION_RESPONSE 't'
+#define NEGOTIATE_PROTOCOL      'v'
+#endif
\ No newline at end of file
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 887ca5e9e1..50ed4011c5 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -43,6 +43,7 @@
 #include "fe-auth.h"
 #include "fe-auth-sasl.h"
 #include "libpq-fe.h"
+#include "protocol.h"
 
 #ifdef ENABLE_GSS
 /*
@@ -586,7 +587,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	/*
 	 * Build a SASLInitialResponse message, and send it.
 	 */
-	if (pqPutMsgStart('p', conn))
+	if (pqPutMsgStart(GSS_RESPONSE, conn))
 		goto error;
 	if (pqPuts(selected_mechanism, conn))
 		goto error;
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 837c5321aa..d780c235bf 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -32,6 +32,7 @@
 #include "mb/pg_wchar.h"
 #include "pg_config_paths.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 
 #ifdef WIN32
 #include "win32.h"
@@ -3591,7 +3592,7 @@ keep_going:						/* We will come back to here until there is
 				 * Anything else probably means it's not Postgres on the other
 				 * end at all.
 				 */
-				if (!(beresp == 'R' || beresp == 'v' || beresp == 'E'))
+				if (!(beresp == AUTHENTICATION_REQUEST || beresp == NEGOTIATE_PROTOCOL || beresp == ERROR_RESPONSE))
 				{
 					libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
 											beresp);
@@ -3618,19 +3619,19 @@ keep_going:						/* We will come back to here until there is
 				 * version 14, the server also used the old protocol for
 				 * errors that happened before processing the startup packet.)
 				 */
-				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
+				if (beresp == AUTHENTICATION_REQUEST && (msgLength < 8 || msgLength > 2000))
 				{
 					libpq_append_conn_error(conn, "received invalid authentication request");
 					goto error_return;
 				}
-				if (beresp == 'v' && (msgLength < 8 || msgLength > 2000))
+				if (beresp == NEGOTIATE_PROTOCOL && (msgLength < 8 || msgLength > 2000))
 				{
 					libpq_append_conn_error(conn, "received invalid protocol negotiation message");
 					goto error_return;
 				}
 
 #define MAX_ERRLEN 30000
-				if (beresp == 'E' && (msgLength < 8 || msgLength > MAX_ERRLEN))
+				if (beresp == ERROR_RESPONSE && (msgLength < 8 || msgLength > MAX_ERRLEN))
 				{
 					/* Handle error from a pre-3.0 server */
 					conn->inCursor = conn->inStart + 1; /* reread data */
@@ -3693,7 +3694,7 @@ keep_going:						/* We will come back to here until there is
 				}
 
 				/* Handle errors. */
-				if (beresp == 'E')
+				if (beresp == ERROR_RESPONSE)
 				{
 					if (pqGetErrorNotice3(conn, true))
 					{
@@ -3770,7 +3771,7 @@ keep_going:						/* We will come back to here until there is
 
 					goto error_return;
 				}
-				else if (beresp == 'v')
+				else if (beresp == NEGOTIATE_PROTOCOL)
 				{
 					if (pqGetNegotiateProtocolVersion3(conn))
 					{
@@ -4540,7 +4541,7 @@ sendTerminateConn(PGconn *conn)
 		 * Try to send "close connection" message to backend. Ignore any
 		 * error.
 		 */
-		pqPutMsgStart('X', conn);
+		pqPutMsgStart(TERMINATE_REQUEST, conn);
 		pqPutMsgEnd(conn);
 		(void) pqFlush(conn);
 	}
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index a868284ff8..461c5e2963 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -27,6 +27,7 @@
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
+#include "protocol.h"
 
 /* keep this in same order as ExecStatusType in libpq-fe.h */
 char	   *const pgresStatus[] = {
@@ -1458,7 +1459,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 
 	/* Send the query message(s) */
 	/* construct the outgoing Query message */
-	if (pqPutMsgStart('Q', conn) < 0 ||
+	if (pqPutMsgStart(SIMPLE_QUERY, conn) < 0 ||
 		pqPuts(query, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 	{
@@ -1571,7 +1572,7 @@ PQsendPrepare(PGconn *conn,
 		return 0;				/* error msg already set */
 
 	/* construct the Parse message */
-	if (pqPutMsgStart('P', conn) < 0 ||
+	if (pqPutMsgStart(PARSE_REQUEST, conn) < 0 ||
 		pqPuts(stmtName, conn) < 0 ||
 		pqPuts(query, conn) < 0)
 		goto sendFailed;
@@ -1599,7 +1600,7 @@ PQsendPrepare(PGconn *conn,
 	/* Add a Sync, unless in pipeline mode. */
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(SYNC_DATA_REQUEST, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			goto sendFailed;
 	}
@@ -1784,7 +1785,7 @@ PQsendQueryGuts(PGconn *conn,
 	if (command)
 	{
 		/* construct the Parse message */
-		if (pqPutMsgStart('P', conn) < 0 ||
+		if (pqPutMsgStart(PARSE_REQUEST, conn) < 0 ||
 			pqPuts(stmtName, conn) < 0 ||
 			pqPuts(command, conn) < 0)
 			goto sendFailed;
@@ -1808,7 +1809,7 @@ PQsendQueryGuts(PGconn *conn,
 	}
 
 	/* Construct the Bind message */
-	if (pqPutMsgStart('B', conn) < 0 ||
+	if (pqPutMsgStart(BIND_REQUEST, conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPuts(stmtName, conn) < 0)
 		goto sendFailed;
@@ -1874,14 +1875,14 @@ PQsendQueryGuts(PGconn *conn,
 		goto sendFailed;
 
 	/* construct the Describe Portal message */
-	if (pqPutMsgStart('D', conn) < 0 ||
+	if (pqPutMsgStart(DESCRIBE_PORTAL, conn) < 0 ||
 		pqPutc('P', conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Execute message */
-	if (pqPutMsgStart('E', conn) < 0 ||
+	if (pqPutMsgStart(EXECUTE_REQUEST, conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutInt(0, 4, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
@@ -1890,7 +1891,7 @@ PQsendQueryGuts(PGconn *conn,
 	/* construct the Sync message if not in pipeline mode */
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(SYNC_DATA_REQUEST, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			goto sendFailed;
 	}
@@ -2422,7 +2423,7 @@ PQdescribePrepared(PGconn *conn, const char *stmt)
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendTypedCommand(conn, 'D', 'S', stmt))
+	if (!PQsendTypedCommand(conn, DATA_ROW_RESPONSE, DESCRIBE_PREPARED, stmt))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -2441,7 +2442,7 @@ PQdescribePortal(PGconn *conn, const char *portal)
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendTypedCommand(conn, 'D', 'P', portal))
+	if (!PQsendTypedCommand(conn, DATA_ROW_RESPONSE, DESCRIBE_PORTAL, portal))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -2456,7 +2457,7 @@ PQdescribePortal(PGconn *conn, const char *portal)
 int
 PQsendDescribePrepared(PGconn *conn, const char *stmt)
 {
-	return PQsendTypedCommand(conn, 'D', 'S', stmt);
+	return PQsendTypedCommand(conn, DATA_ROW_RESPONSE, DESCRIBE_PREPARED, stmt);
 }
 
 /*
@@ -2469,7 +2470,7 @@ PQsendDescribePrepared(PGconn *conn, const char *stmt)
 int
 PQsendDescribePortal(PGconn *conn, const char *portal)
 {
-	return PQsendTypedCommand(conn, 'D', 'P', portal);
+	return PQsendTypedCommand(conn, DATA_ROW_RESPONSE, DESCRIBE_PORTAL, portal);
 }
 
 /*
@@ -2577,7 +2578,7 @@ PQsendTypedCommand(PGconn *conn, char command, char type, const char *target)
 	/* construct the Sync message */
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(SYNC_DATA_REQUEST, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			goto sendFailed;
 	}
@@ -2696,7 +2697,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 				return pqIsnonblocking(conn) ? 0 : -1;
 		}
 		/* Send the data (too simple to delegate to fe-protocol files) */
-		if (pqPutMsgStart('d', conn) < 0 ||
+		if (pqPutMsgStart(COPY_DATA, conn) < 0 ||
 			pqPutnchar(buffer, nbytes, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
@@ -2731,7 +2732,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (errormsg)
 	{
 		/* Send COPY FAIL */
-		if (pqPutMsgStart('f', conn) < 0 ||
+		if (pqPutMsgStart(COPY_FAIL, conn) < 0 ||
 			pqPuts(errormsg, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
@@ -2739,7 +2740,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	else
 	{
 		/* Send COPY DONE */
-		if (pqPutMsgStart('c', conn) < 0 ||
+		if (pqPutMsgStart(COPY_DONE, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
 	}
@@ -2751,7 +2752,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (conn->cmd_queue_head &&
 		conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(SYNC_DATA_REQUEST, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
 	}
@@ -3263,7 +3264,7 @@ PQpipelineSync(PGconn *conn)
 	entry->query = NULL;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', conn) < 0 ||
+	if (pqPutMsgStart(SYNC_DATA_REQUEST, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -3311,7 +3312,7 @@ PQsendFlushRequest(PGconn *conn)
 		return 0;
 	}
 
-	if (pqPutMsgStart('H', conn) < 0 ||
+	if (pqPutMsgStart(FLUSH_DATA_REQUEST, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 	{
 		return 0;
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 7bc6355d17..6cbf945428 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -28,14 +28,16 @@
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 
 /*
  * This macro lists the backend message types that could be "long" (more
  * than a couple of kilobytes).
  */
 #define VALID_LONG_MESSAGE_TYPE(id) \
-	((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \
-	 (id) == 'E' || (id) == 'N' || (id) == 'A')
+	((id) == ROW_DESCRIPTION_RESPONSE || (id) == DATA_ROW_RESPONSE || (id) == COPY_DATA || \
+	 (id) == FUNCTION_CALL_RESPONSE ||  (id) == ERROR_RESPONSE || (id) == NOTICE_RESPONSE || \
+	 (id) == NOTIFY_RESPONSE)
 
 
 static void handleSyncLoss(PGconn *conn, char id, int msgLength);
@@ -140,12 +142,12 @@ pqParseInput3(PGconn *conn)
 		 * from config file due to SIGHUP), but otherwise we hold off until
 		 * BUSY state.
 		 */
-		if (id == 'A')
+		if (id == NOTIFY_RESPONSE)
 		{
 			if (getNotify(conn))
 				return;
 		}
-		else if (id == 'N')
+		else if (id == NOTICE_RESPONSE)
 		{
 			if (pqGetErrorNotice3(conn, false))
 				return;
@@ -165,12 +167,12 @@ pqParseInput3(PGconn *conn)
 			 * it is about to close the connection, so we don't want to just
 			 * discard it...)
 			 */
-			if (id == 'E')
+			if (id == ERROR_RESPONSE)
 			{
 				if (pqGetErrorNotice3(conn, false /* treat as notice */ ))
 					return;
 			}
-			else if (id == 'S')
+			else if (id == PARAMETER_STATUS_RESPONSE)
 			{
 				if (getParameterStatus(conn))
 					return;
@@ -192,7 +194,7 @@ pqParseInput3(PGconn *conn)
 			 */
 			switch (id)
 			{
-				case 'C':		/* command complete */
+				case COMMAND_COMPLETE:
 					if (pqGets(&conn->workBuffer, conn))
 						return;
 					if (!pgHavePendingResult(conn))
@@ -210,12 +212,12 @@ pqParseInput3(PGconn *conn)
 								CMDSTATUS_LEN);
 					conn->asyncStatus = PGASYNC_READY;
 					break;
-				case 'E':		/* error return */
+				case ERROR_RESPONSE:
 					if (pqGetErrorNotice3(conn, true))
 						return;
 					conn->asyncStatus = PGASYNC_READY;
 					break;
-				case 'Z':		/* sync response, backend is ready for new
+				case READY_FOR_QUERY:		/* sync response, backend is ready for new
 								 * query */
 					if (getReadyForQuery(conn))
 						return;
@@ -246,7 +248,7 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_IDLE;
 					}
 					break;
-				case 'I':		/* empty query */
+				case EMPTY_QUERY_RESPONSE:
 					if (!pgHavePendingResult(conn))
 					{
 						conn->result = PQmakeEmptyPGresult(conn,
@@ -259,7 +261,7 @@ pqParseInput3(PGconn *conn)
 					}
 					conn->asyncStatus = PGASYNC_READY;
 					break;
-				case '1':		/* Parse Complete */
+				case PARSE_COMPLETE_RESPONSE:
 					/* If we're doing PQprepare, we're done; else ignore */
 					if (conn->cmd_queue_head &&
 						conn->cmd_queue_head->queryclass == PGQUERY_PREPARE)
@@ -277,10 +279,10 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_READY;
 					}
 					break;
-				case '2':		/* Bind Complete */
+				case BIND_COMPLETE_RESPONSE:
 					/* Nothing to do for this message type */
 					break;
-				case '3':		/* Close Complete */
+				case CLOSE_COMPLETE_RESPONSE:
 					/* If we're doing PQsendClose, we're done; else ignore */
 					if (conn->cmd_queue_head &&
 						conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
@@ -298,11 +300,11 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_READY;
 					}
 					break;
-				case 'S':		/* parameter status */
+				case PARAMETER_STATUS_RESPONSE:
 					if (getParameterStatus(conn))
 						return;
 					break;
-				case 'K':		/* secret key data from the backend */
+				case BACKEND_KEY_DATA:		/* secret key data from the backend */
 
 					/*
 					 * This is expected only during backend startup, but it's
@@ -314,7 +316,7 @@ pqParseInput3(PGconn *conn)
 					if (pqGetInt(&(conn->be_key), 4, conn))
 						return;
 					break;
-				case 'T':		/* Row Description */
+				case ROW_DESCRIPTION_RESPONSE:
 					if (conn->error_result ||
 						(conn->result != NULL &&
 						 conn->result->resultStatus == PGRES_FATAL_ERROR))
@@ -346,7 +348,7 @@ pqParseInput3(PGconn *conn)
 						return;
 					}
 					break;
-				case 'n':		/* No Data */
+				case NO_DATA_RESPONSE:
 
 					/*
 					 * NoData indicates that we will not be seeing a
@@ -374,11 +376,11 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_READY;
 					}
 					break;
-				case 't':		/* Parameter Description */
+				case PARAMETER_DESCRIPTION_RESPONSE:
 					if (getParamDescriptions(conn, msgLength))
 						return;
 					break;
-				case 'D':		/* Data Row */
+				case DATA_ROW_RESPONSE:
 					if (conn->result != NULL &&
 						conn->result->resultStatus == PGRES_TUPLES_OK)
 					{
@@ -405,24 +407,24 @@ pqParseInput3(PGconn *conn)
 						conn->inCursor += msgLength;
 					}
 					break;
-				case 'G':		/* Start Copy In */
+				case COPY_IN_RESPONSE:
 					if (getCopyStart(conn, PGRES_COPY_IN))
 						return;
 					conn->asyncStatus = PGASYNC_COPY_IN;
 					break;
-				case 'H':		/* Start Copy Out */
+				case COPY_OUT_RESPONSE:
 					if (getCopyStart(conn, PGRES_COPY_OUT))
 						return;
 					conn->asyncStatus = PGASYNC_COPY_OUT;
 					conn->copy_already_done = 0;
 					break;
-				case 'W':		/* Start Copy Both */
+				case COPY_BOTH_RESPONSE:
 					if (getCopyStart(conn, PGRES_COPY_BOTH))
 						return;
 					conn->asyncStatus = PGASYNC_COPY_BOTH;
 					conn->copy_already_done = 0;
 					break;
-				case 'd':		/* Copy Data */
+				case COPY_DATA:
 
 					/*
 					 * If we see Copy Data, just silently drop it.  This would
@@ -431,7 +433,7 @@ pqParseInput3(PGconn *conn)
 					 */
 					conn->inCursor += msgLength;
 					break;
-				case 'c':		/* Copy Done */
+				case COPY_DONE:
 
 					/*
 					 * If we see Copy Done, just silently drop it.  This is
@@ -1929,7 +1931,7 @@ pqEndcopy3(PGconn *conn)
 	if (conn->asyncStatus == PGASYNC_COPY_IN ||
 		conn->asyncStatus == PGASYNC_COPY_BOTH)
 	{
-		if (pqPutMsgStart('c', conn) < 0 ||
+		if (pqPutMsgStart(COPY_DONE, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return 1;
 
@@ -1940,7 +1942,7 @@ pqEndcopy3(PGconn *conn)
 		if (conn->cmd_queue_head &&
 			conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE)
 		{
-			if (pqPutMsgStart('S', conn) < 0 ||
+			if (pqPutMsgStart(SYNC_DATA_REQUEST, conn) < 0 ||
 				pqPutMsgEnd(conn) < 0)
 				return 1;
 		}
@@ -2023,7 +2025,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 
 	/* PQfn already validated connection state */
 
-	if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
+	if (pqPutMsgStart(FUNCTION_CALL_REQUEST, conn) < 0 || /* function call msg */
 		pqPutInt(fnid, 4, conn) < 0 ||	/* function id */
 		pqPutInt(1, 2, conn) < 0 || /* # of format codes */
 		pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index 402784f40e..3f4381a323 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -28,6 +28,7 @@
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 
 
 /* Enable tracing */
@@ -562,110 +563,110 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 
 	switch (id)
 	{
-		case '1':
+		case PARSE_COMPLETE_RESPONSE:
 			fprintf(conn->Pfdebug, "ParseComplete");
 			/* No message content */
 			break;
-		case '2':
+		case BIND_COMPLETE_RESPONSE:
 			fprintf(conn->Pfdebug, "BindComplete");
 			/* No message content */
 			break;
-		case '3':
+		case CLOSE_COMPLETE_RESPONSE:
 			fprintf(conn->Pfdebug, "CloseComplete");
 			/* No message content */
 			break;
-		case 'A':				/* Notification Response */
+		case NOTIFY_RESPONSE:
 			pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'B':				/* Bind */
+		case BIND_REQUEST:
 			pqTraceOutputB(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'c':
+		case COPY_DONE:
 			fprintf(conn->Pfdebug, "CopyDone");
 			/* No message content */
 			break;
-		case 'C':				/* Close(F) or Command Complete(B) */
+		case COMMAND_COMPLETE:				/* Close(F) or Command Complete(B) */
 			pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor);
 			break;
-		case 'd':				/* Copy Data */
+		case COPY_DATA:
 			/* Drop COPY data to reduce the overhead of logging. */
 			break;
-		case 'D':				/* Describe(F) or Data Row(B) */
+		case DESCRIBE_REQUEST:		/* Describe(F) or Data Row(B) */
 			pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor);
 			break;
-		case 'E':				/* Execute(F) or Error Response(B) */
+		case EXECUTE_REQUEST:		/* Execute(F) or Error Response(B) */
 			pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor,
 						   regress);
 			break;
-		case 'f':				/* Copy Fail */
+		case COPY_FAIL:
 			pqTraceOutputf(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'F':				/* Function Call */
+		case FUNCTION_CALL_REQUEST:
 			pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'G':				/* Start Copy In */
+		case COPY_IN_RESPONSE:
 			pqTraceOutputG(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'H':				/* Flush(F) or Start Copy Out(B) */
+		case FLUSH_DATA_REQUEST:	/* Flush(F) or Start Copy Out(B) */
 			if (!toServer)
 				pqTraceOutputH(conn->Pfdebug, message, &logCursor);
 			else
 				fprintf(conn->Pfdebug, "Flush");	/* no message content */
 			break;
-		case 'I':
+		case EMPTY_QUERY_RESPONSE:
 			fprintf(conn->Pfdebug, "EmptyQueryResponse");
 			/* No message content */
 			break;
-		case 'K':				/* secret key data from the backend */
+		case BACKEND_KEY_DATA:		/* secret key data from the backend */
 			pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'n':
+		case NO_DATA_RESPONSE:
 			fprintf(conn->Pfdebug, "NoData");
 			/* No message content */
 			break;
-		case 'N':
+		case NOTICE_RESPONSE:
 			pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message,
 							&logCursor, regress);
 			break;
-		case 'P':				/* Parse */
+		case PARSE_REQUEST:
 			pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'Q':				/* Query */
+		case SIMPLE_QUERY:
 			pqTraceOutputQ(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'R':				/* Authentication */
+		case AUTHENTICATION_REQUEST:
 			pqTraceOutputR(conn->Pfdebug, message, &logCursor);
 			break;
-		case 's':
+		case PORTAL_SUSPENDED_RESPONSE:
 			fprintf(conn->Pfdebug, "PortalSuspended");
 			/* No message content */
 			break;
-		case 'S':				/* Parameter Status(B) or Sync(F) */
+		case SYNC_DATA_REQUEST:	/* Parameter Status(B) or Sync(F) */
 			if (!toServer)
 				pqTraceOutputS(conn->Pfdebug, message, &logCursor);
 			else
 				fprintf(conn->Pfdebug, "Sync"); /* no message content */
 			break;
-		case 't':				/* Parameter Description */
+		case PARAMETER_DESCRIPTION_RESPONSE:
 			pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'T':				/* Row Description */
+		case ROW_DESCRIPTION_RESPONSE:
 			pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'v':				/* Negotiate Protocol Version */
+		case NEGOTIATE_PROTOCOL:
 			pqTraceOutputv(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'V':				/* Function Call response */
+		case FUNCTION_CALL_RESPONSE:
 			pqTraceOutputV(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'W':				/* Start Copy Both */
+		case COPY_BOTH_RESPONSE:
 			pqTraceOutputW(conn->Pfdebug, message, &logCursor, length);
 			break;
-		case 'X':
+		case TERMINATE_REQUEST:
 			fprintf(conn->Pfdebug, "Terminate");
 			/* No message content */
 			break;
-		case 'Z':				/* Ready For Query */
+		case READY_FOR_QUERY:
 			pqTraceOutputZ(conn->Pfdebug, message, &logCursor);
 			break;
 		default:
-- 
2.37.1 (Apple Git-137.1)



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 17:59  Alvaro Herrera <[email protected]>
  parent: Dave Cramer <[email protected]>
  1 sibling, 1 reply; 80+ messages in thread

From: Alvaro Herrera @ 2023-08-03 17:59 UTC (permalink / raw)
  To: Dave Cramer <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; [email protected]

On 2023-Aug-03, Dave Cramer wrote:

> Greetings,
> 
> Attached is a patch which introduces a file protocol.h. Instead of using
> the actual characters everywhere in the code this patch names the
> characters and removes the comments beside each usage.

I saw this one last week.  I think it's a very idea (and I fact I had
thought of doing it when I last messed with libpq code).

I don't really like the name pattern you've chosen though; I think we
need to have a common prefix in the defines.  Maybe prepending PQMSG_ to
each name would be enough.  And maybe turn the _RESPONSE and _REQUEST
suffixes you added into prefixes as well, so instead of PARSE_REQUEST
you could make it PQMSG_REQ_PARSE, PQMSG_RESP_BIND_COMPLETE and so
on.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
<Schwern> It does it in a really, really complicated way
<crab> why does it need to be complicated?
<Schwern> Because it's MakeMaker.






^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 18:07  Dave Cramer <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  0 siblings, 1 reply; 80+ messages in thread

From: Dave Cramer @ 2023-08-03 18:07 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>; [email protected]

On Thu, 3 Aug 2023 at 11:59, Alvaro Herrera <[email protected]> wrote:

> On 2023-Aug-03, Dave Cramer wrote:
>
> > Greetings,
> >
> > Attached is a patch which introduces a file protocol.h. Instead of using
> > the actual characters everywhere in the code this patch names the
> > characters and removes the comments beside each usage.
>
> I saw this one last week.  I think it's a very idea (and I fact I had
> thought of doing it when I last messed with libpq code).
>
> I don't really like the name pattern you've chosen though; I think we
> need to have a common prefix in the defines.  Maybe prepending PQMSG_ to
> each name would be enough.  And maybe turn the _RESPONSE and _REQUEST
> suffixes you added into prefixes as well, so instead of PARSE_REQUEST
> you could make it PQMSG_REQ_PARSE, PQMSG_RESP_BIND_COMPLETE and so
> on.
>
That becomes trivial to do now that the names are defined. I presumed
someone would object to the names.
I'm fine with the names you propose, but I suggest we wait to see if anyone
objects.

Dave

>
> --
> Álvaro Herrera               48°01'N 7°57'E  —
> https://www.EnterpriseDB.com/
> <Schwern> It does it in a really, really complicated way
> <crab> why does it need to be complicated?
> <Schwern> Because it's MakeMaker.
>


^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 18:53  Nathan Bossart <[email protected]>
  parent: Dave Cramer <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Nathan Bossart @ 2023-08-03 18:53 UTC (permalink / raw)
  To: Dave Cramer <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; PostgreSQL Hackers <[email protected]>

On Thu, Aug 03, 2023 at 12:07:21PM -0600, Dave Cramer wrote:
> On Thu, 3 Aug 2023 at 11:59, Alvaro Herrera <[email protected]> wrote:
>> I don't really like the name pattern you've chosen though; I think we
>> need to have a common prefix in the defines.  Maybe prepending PQMSG_ to
>> each name would be enough.  And maybe turn the _RESPONSE and _REQUEST
>> suffixes you added into prefixes as well, so instead of PARSE_REQUEST
>> you could make it PQMSG_REQ_PARSE, PQMSG_RESP_BIND_COMPLETE and so
>> on.
>>
> That becomes trivial to do now that the names are defined. I presumed
> someone would object to the names.
> I'm fine with the names you propose, but I suggest we wait to see if anyone
> objects.

I'm okay with the proposed names as well.

> + * src/include/protocol.h

Could we put these definitions in an existing header such as
src/include/libpq/pqcomm.h?  I see that's where the authentication request
codes live today.

-- 
Nathan Bossart
Amazon Web Services: https://aws.amazon.com






^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 19:25  Tatsuo Ishii <[email protected]>
  parent: Dave Cramer <[email protected]>
  1 sibling, 1 reply; 80+ messages in thread

From: Tatsuo Ishii @ 2023-08-03 19:25 UTC (permalink / raw)
  To: [email protected]; +Cc: [email protected]; [email protected]

> Greetings,
> 
> Attached is a patch which introduces a file protocol.h. Instead of using
> the actual characters everywhere in the code this patch names the
> characters and removes the comments beside each usage.

> +#define DESCRIBE_PREPARED           'S'
> +#define DESCRIBE_PORTAL             'P'

You use these for Close message as well. I don't like the idea because
Close is different message from Describe message.

What about adding following for Close too use them instead?

#define CLOSE_PREPARED           'S'
#define CLOSE_PORTAL             'P'

Best reagards,
--
Tatsuo Ishii
SRA OSS LLC
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp






^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 21:22  Dave Cramer <[email protected]>
  parent: Tatsuo Ishii <[email protected]>
  0 siblings, 2 replies; 80+ messages in thread

From: Dave Cramer @ 2023-08-03 21:22 UTC (permalink / raw)
  To: Tatsuo Ishii <[email protected]>; +Cc: [email protected]; [email protected]

On Thu, 3 Aug 2023 at 13:25, Tatsuo Ishii <[email protected]> wrote:

> > Greetings,
> >
> > Attached is a patch which introduces a file protocol.h. Instead of using
> > the actual characters everywhere in the code this patch names the
> > characters and removes the comments beside each usage.
>
> > +#define DESCRIBE_PREPARED           'S'
> > +#define DESCRIBE_PORTAL             'P'
>
> You use these for Close message as well. I don't like the idea because
> Close is different message from Describe message.
>
> What about adding following for Close too use them instead?
>
> #define CLOSE_PREPARED           'S'
> #define CLOSE_PORTAL             'P'
>

Good catch.
I recall when writing this it was a bit hacky.
What do you think of PREPARED_SUB_COMMAND   and PORTAL_SUB_COMMAND instead
of duplicating them ?

Dave


^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 21:42  Dave Cramer <[email protected]>
  parent: Dave Cramer <[email protected]>
  1 sibling, 0 replies; 80+ messages in thread

From: Dave Cramer @ 2023-08-03 21:42 UTC (permalink / raw)
  To: Tatsuo Ishii <[email protected]>; +Cc: [email protected]; [email protected]

On Thu, 3 Aug 2023 at 15:22, Dave Cramer <[email protected]> wrote:

>
>
> On Thu, 3 Aug 2023 at 13:25, Tatsuo Ishii <[email protected]> wrote:
>
>> > Greetings,
>> >
>> > Attached is a patch which introduces a file protocol.h. Instead of using
>> > the actual characters everywhere in the code this patch names the
>> > characters and removes the comments beside each usage.
>>
>> > +#define DESCRIBE_PREPARED           'S'
>> > +#define DESCRIBE_PORTAL             'P'
>>
>> You use these for Close message as well. I don't like the idea because
>> Close is different message from Describe message.
>>
>> What about adding following for Close too use them instead?
>>
>> #define CLOSE_PREPARED           'S'
>> #define CLOSE_PORTAL             'P'
>>
>
> Good catch.
> I recall when writing this it was a bit hacky.
> What do you think of PREPARED_SUB_COMMAND   and PORTAL_SUB_COMMAND instead
> of duplicating them ?
>

While reviewing this I found a number of mistakes where I used
DATA_ROW_RESPONSE instead of DESCRIBE_REQUEST.

I can provide a patch now or wait until we resolve the above


>
> Dave
>


^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-03 22:59  Tatsuo Ishii <[email protected]>
  parent: Dave Cramer <[email protected]>
  1 sibling, 1 reply; 80+ messages in thread

From: Tatsuo Ishii @ 2023-08-03 22:59 UTC (permalink / raw)
  To: [email protected]; +Cc: [email protected]; [email protected]

> On Thu, 3 Aug 2023 at 13:25, Tatsuo Ishii <[email protected]> wrote:
> 
>> > Greetings,
>> >
>> > Attached is a patch which introduces a file protocol.h. Instead of using
>> > the actual characters everywhere in the code this patch names the
>> > characters and removes the comments beside each usage.
>>
>> > +#define DESCRIBE_PREPARED           'S'
>> > +#define DESCRIBE_PORTAL             'P'
>>
>> You use these for Close message as well. I don't like the idea because
>> Close is different message from Describe message.
>>
>> What about adding following for Close too use them instead?
>>
>> #define CLOSE_PREPARED           'S'
>> #define CLOSE_PORTAL             'P'
>>
> 
> Good catch.
> I recall when writing this it was a bit hacky.
> What do you think of PREPARED_SUB_COMMAND   and PORTAL_SUB_COMMAND instead
> of duplicating them ?

Nice. Looks good to me.

Best reagards,
--
Tatsuo Ishii
SRA OSS LLC
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp






^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-04 02:09  Dave Cramer <[email protected]>
  parent: Tatsuo Ishii <[email protected]>
  0 siblings, 1 reply; 80+ messages in thread

From: Dave Cramer @ 2023-08-04 02:09 UTC (permalink / raw)
  To: Tatsuo Ishii <[email protected]>; +Cc: [email protected]; [email protected]

On Thu, 3 Aug 2023 at 16:59, Tatsuo Ishii <[email protected]> wrote:

> > On Thu, 3 Aug 2023 at 13:25, Tatsuo Ishii <[email protected]> wrote:
> >
> >> > Greetings,
> >> >
> >> > Attached is a patch which introduces a file protocol.h. Instead of
> using
> >> > the actual characters everywhere in the code this patch names the
> >> > characters and removes the comments beside each usage.
> >>
> >> > +#define DESCRIBE_PREPARED           'S'
> >> > +#define DESCRIBE_PORTAL             'P'
> >>
> >> You use these for Close message as well. I don't like the idea because
> >> Close is different message from Describe message.
> >>
> >> What about adding following for Close too use them instead?
> >>
> >> #define CLOSE_PREPARED           'S'
> >> #define CLOSE_PORTAL             'P'
> >>
> >
> > Good catch.
> > I recall when writing this it was a bit hacky.
> > What do you think of PREPARED_SUB_COMMAND   and PORTAL_SUB_COMMAND
> instead
> > of duplicating them ?
>
> Nice. Looks good to me.
>
New patch attached which uses  PREPARED_SUB_COMMAND   and
PORTAL_SUB_COMMAND instead
and uses
PQMSG_REQ_* and  PQMSG_RESP_*  as per Alvaro's suggestion

Dave Cramer


Attachments:

  [application/octet-stream] 0001-Created-protocol.h.patch (53.2K, ../../CADK3HH+-=08kd-aNCOP9s7fCZHUxrUBvVx5cyBJZzHD49wzPQA@mail.gmail.com/3-0001-Created-protocol.h.patch)
  download | inline diff:
From 45733bb77f3ad2b7665e52c9e24b3aa11699f56e Mon Sep 17 00:00:00 2001
From: Dave Cramer <[email protected]>
Date: Thu, 20 Apr 2023 15:40:03 -0400
Subject: [PATCH] Created protocol.h Protocol.h has defines for every protocol
 message both backend and frontend Instead of using hardcoded values for each
 protocol message use defines to make code easier to read

remove redundant comments

define CLOSE_PORTAL and CLOSE_PREPARED subcommands
fix some erroneous uses of DATA_ROW_RESPONSE which should have been DESCRIBE_REQUEST
use the names in the requests .

prepend PQMSG_REQ and PQMSG_RESP to commands and responses respectively
---
 src/backend/access/common/printsimple.c       |  5 +-
 src/backend/access/transam/parallel.c         | 17 ++--
 src/backend/backup/basebackup_copy.c          | 17 ++--
 src/backend/commands/async.c                  |  3 +-
 src/backend/commands/copyfromparse.c          | 23 +++---
 src/backend/commands/copyto.c                 |  5 +-
 src/backend/libpq/auth-sasl.c                 |  3 +-
 src/backend/libpq/auth.c                      |  9 ++-
 src/backend/postmaster/postmaster.c           |  3 +-
 src/backend/replication/walsender.c           | 19 ++---
 src/backend/tcop/dest.c                       |  5 +-
 src/backend/tcop/fastpath.c                   |  3 +-
 src/backend/tcop/postgres.c                   | 77 ++++++++++---------
 src/backend/utils/activity/backend_progress.c |  3 +-
 src/backend/utils/error/elog.c                |  3 +-
 src/backend/utils/misc/guc.c                  |  3 +-
 src/include/protocol.h                        | 61 +++++++++++++++
 src/interfaces/libpq/fe-auth.c                |  3 +-
 src/interfaces/libpq/fe-connect.c             | 15 ++--
 src/interfaces/libpq/fe-exec.c                | 53 ++++++-------
 src/interfaces/libpq/fe-protocol3.c           | 66 ++++++++--------
 src/interfaces/libpq/fe-trace.c               | 61 +++++++--------
 22 files changed, 270 insertions(+), 187 deletions(-)
 create mode 100644 src/include/protocol.h

diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index ef818228ac..e7dc125f82 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -21,6 +21,7 @@
 #include "access/printsimple.h"
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
+#include "protocol.h"
 #include "utils/builtins.h"
 
 /*
@@ -32,7 +33,7 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc)
 	StringInfoData buf;
 	int			i;
 
-	pq_beginmessage(&buf, 'T'); /* RowDescription */
+	pq_beginmessage(&buf, PQMSG_RESP_ROW_DESCRIPTION);
 	pq_sendint16(&buf, tupdesc->natts);
 
 	for (i = 0; i < tupdesc->natts; ++i)
@@ -65,7 +66,7 @@ printsimple(TupleTableSlot *slot, DestReceiver *self)
 	slot_getallattrs(slot);
 
 	/* Prepare and send message */
-	pq_beginmessage(&buf, 'D');
+	pq_beginmessage(&buf, PQMSG_RESP_DATA_ROW);
 	pq_sendint16(&buf, tupdesc->natts);
 
 	for (i = 0; i < tupdesc->natts; ++i)
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 1738aecf1f..6deb1a697f 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -33,6 +33,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "protocol.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/sinval.h"
@@ -1127,7 +1128,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 
 	switch (msgtype)
 	{
-		case 'K':				/* BackendKeyData */
+		case PQMSG_REQ_BACKEND_KEY_DATA:
 			{
 				int32		pid = pq_getmsgint(msg, 4);
 
@@ -1137,8 +1138,8 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'E':				/* ErrorResponse */
-		case 'N':				/* NoticeResponse */
+		case PQMSG_RESP_ERROR:
+		case PQMSG_RESP_NOTICE:
 			{
 				ErrorData	edata;
 				ErrorContextCallback *save_error_context_stack;
@@ -1183,7 +1184,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'A':				/* NotifyResponse */
+		case PQMSG_RESP_NOTIFY:
 			{
 				/* Propagate NotifyResponse. */
 				int32		pid;
@@ -1200,7 +1201,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'P':				/* Parallel progress reporting */
+		case PQMSG_RESP_PARALLEL_PROGRESS:
 			{
 				/*
 				 * Only incremental progress reporting is currently supported.
@@ -1217,7 +1218,7 @@ HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg)
 				break;
 			}
 
-		case 'X':				/* Terminate, indicating clean exit */
+		case PQMSG_REQ_TERMINATE:	/* Terminate, indicating clean exit */
 			{
 				shm_mq_detach(pcxt->worker[i].error_mqh);
 				pcxt->worker[i].error_mqh = NULL;
@@ -1372,7 +1373,7 @@ ParallelWorkerMain(Datum main_arg)
 	 * protocol message is defined, but it won't actually be used for anything
 	 * in this case.
 	 */
-	pq_beginmessage(&msgbuf, 'K');
+	pq_beginmessage(&msgbuf, PQMSG_REQ_BACKEND_KEY_DATA);
 	pq_sendint32(&msgbuf, (int32) MyProcPid);
 	pq_sendint32(&msgbuf, (int32) MyCancelKey);
 	pq_endmessage(&msgbuf);
@@ -1550,7 +1551,7 @@ ParallelWorkerMain(Datum main_arg)
 	DetachSession();
 
 	/* Report success. */
-	pq_putmessage('X', NULL, 0);
+	pq_putmessage(PQMSG_REQ_TERMINATE, NULL, 0);
 }
 
 /*
diff --git a/src/backend/backup/basebackup_copy.c b/src/backend/backup/basebackup_copy.c
index 1db80cde1b..44547bfa4d 100644
--- a/src/backend/backup/basebackup_copy.c
+++ b/src/backend/backup/basebackup_copy.c
@@ -32,6 +32,7 @@
 #include "executor/executor.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
+#include "protocol.h"
 #include "tcop/dest.h"
 #include "utils/builtins.h"
 #include "utils/timestamp.h"
@@ -169,7 +170,7 @@ bbsink_copystream_begin_archive(bbsink *sink, const char *archive_name)
 	StringInfoData buf;
 
 	ti = list_nth(state->tablespaces, state->tablespace_num);
-	pq_beginmessage(&buf, 'd'); /* CopyData */
+	pq_beginmessage(&buf, PQMSG_REQ_COPY_DATA);
 	pq_sendbyte(&buf, 'n');		/* New archive */
 	pq_sendstring(&buf, archive_name);
 	pq_sendstring(&buf, ti->path == NULL ? "" : ti->path);
@@ -220,8 +221,8 @@ bbsink_copystream_archive_contents(bbsink *sink, size_t len)
 		{
 			mysink->last_progress_report_time = now;
 
-			pq_beginmessage(&buf, 'd'); /* CopyData */
-			pq_sendbyte(&buf, 'p'); /* Progress report */
+			pq_beginmessage(&buf, PQMSG_REQ_COPY_DATA);
+			pq_sendbyte(&buf, PQMSG_REQ_COPY_PROGRESS);
 			pq_sendint64(&buf, state->bytes_done);
 			pq_endmessage(&buf);
 			pq_flush_if_writable();
@@ -246,8 +247,8 @@ bbsink_copystream_end_archive(bbsink *sink)
 
 	mysink->bytes_done_at_last_time_check = state->bytes_done;
 	mysink->last_progress_report_time = GetCurrentTimestamp();
-	pq_beginmessage(&buf, 'd'); /* CopyData */
-	pq_sendbyte(&buf, 'p');		/* Progress report */
+	pq_beginmessage(&buf, PQMSG_REQ_COPY_DATA);
+	pq_sendbyte(&buf, PQMSG_REQ_COPY_PROGRESS);
 	pq_sendint64(&buf, state->bytes_done);
 	pq_endmessage(&buf);
 	pq_flush_if_writable();
@@ -261,7 +262,7 @@ bbsink_copystream_begin_manifest(bbsink *sink)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf, 'd'); /* CopyData */
+	pq_beginmessage(&buf, PQMSG_REQ_COPY_DATA);
 	pq_sendbyte(&buf, 'm');		/* Manifest */
 	pq_endmessage(&buf);
 }
@@ -318,7 +319,7 @@ SendCopyOutResponse(void)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf, 'H');
+	pq_beginmessage(&buf, PQMSG_RESP_COPY_OUT);
 	pq_sendbyte(&buf, 0);		/* overall format */
 	pq_sendint16(&buf, 0);		/* natts */
 	pq_endmessage(&buf);
@@ -330,7 +331,7 @@ SendCopyOutResponse(void)
 static void
 SendCopyDone(void)
 {
-	pq_putemptymessage('c');
+	pq_putemptymessage(PQMSG_REQ_COPY_DONE);
 }
 
 /*
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index ef909cf4e0..e8de47bd41 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -141,6 +141,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "protocol.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -2281,7 +2282,7 @@ NotifyMyFrontEnd(const char *channel, const char *payload, int32 srcPid)
 	{
 		StringInfoData buf;
 
-		pq_beginmessage(&buf, 'A');
+		pq_beginmessage(&buf, PQMSG_RESP_NOTIFY);
 		pq_sendint32(&buf, srcPid);
 		pq_sendstring(&buf, channel);
 		pq_sendstring(&buf, payload);
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 232768a6e1..7bae4a55c2 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -72,6 +72,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -174,7 +175,7 @@ ReceiveCopyBegin(CopyFromState cstate)
 	int16		format = (cstate->opts.binary ? 1 : 0);
 	int			i;
 
-	pq_beginmessage(&buf, 'G');
+	pq_beginmessage(&buf, PQMSG_RESP_COPY_IN);
 	pq_sendbyte(&buf, format);	/* overall format */
 	pq_sendint16(&buf, natts);
 	for (i = 0; i < natts; i++)
@@ -279,13 +280,13 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 					/* Validate message type and set packet size limit */
 					switch (mtype)
 					{
-						case 'd':	/* CopyData */
+						case PQMSG_REQ_COPY_DATA:
 							maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 							break;
-						case 'c':	/* CopyDone */
-						case 'f':	/* CopyFail */
-						case 'H':	/* Flush */
-						case 'S':	/* Sync */
+						case PQMSG_REQ_COPY_DONE:
+						case PQMSG_REQ_COPY_FAIL:
+						case PQMSG_REQ_FLUSH_DATA:
+						case PQMSG_REQ_SYNC_DATA:
 							maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 							break;
 						default:
@@ -305,20 +306,20 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 					/* ... and process it */
 					switch (mtype)
 					{
-						case 'd':	/* CopyData */
+						case PQMSG_REQ_COPY_DATA:
 							break;
-						case 'c':	/* CopyDone */
+						case PQMSG_REQ_COPY_DONE:
 							/* COPY IN correctly terminated by frontend */
 							cstate->raw_reached_eof = true;
 							return bytesread;
-						case 'f':	/* CopyFail */
+						case PQMSG_REQ_COPY_FAIL:
 							ereport(ERROR,
 									(errcode(ERRCODE_QUERY_CANCELED),
 									 errmsg("COPY from stdin failed: %s",
 											pq_getmsgstring(cstate->fe_msgbuf))));
 							break;
-						case 'H':	/* Flush */
-						case 'S':	/* Sync */
+						case PQMSG_REQ_FLUSH_DATA:
+						case PQMSG_REQ_SYNC_DATA:
 
 							/*
 							 * Ignore Flush/Sync for the convenience of client
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index 9e4b2437a5..f11b295787 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -34,6 +34,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "protocol.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
@@ -144,7 +145,7 @@ SendCopyBegin(CopyToState cstate)
 	int16		format = (cstate->opts.binary ? 1 : 0);
 	int			i;
 
-	pq_beginmessage(&buf, 'H');
+	pq_beginmessage(&buf, PQMSG_RESP_COPY_OUT);
 	pq_sendbyte(&buf, format);	/* overall format */
 	pq_sendint16(&buf, natts);
 	for (i = 0; i < natts; i++)
@@ -159,7 +160,7 @@ SendCopyEnd(CopyToState cstate)
 	/* Shouldn't have any unsent data */
 	Assert(cstate->fe_msgbuf->len == 0);
 	/* Send Copy Done message */
-	pq_putemptymessage('c');
+	pq_putemptymessage(PQMSG_REQ_COPY_DONE);
 }
 
 /*----------
diff --git a/src/backend/libpq/auth-sasl.c b/src/backend/libpq/auth-sasl.c
index 684680897b..6c2948a1dd 100644
--- a/src/backend/libpq/auth-sasl.c
+++ b/src/backend/libpq/auth-sasl.c
@@ -19,6 +19,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/sasl.h"
+#include "protocol.h"
 
 /*
  * Maximum accepted size of SASL messages.
@@ -87,7 +88,7 @@ CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
 	{
 		pq_startmsgread();
 		mtype = pq_getbyte();
-		if (mtype != 'p')
+		if (mtype != PQMSG_RESP_GSS)
 		{
 			/* Only log error if client didn't disconnect. */
 			if (mtype != EOF)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 315a24bb3f..61dfbb9a99 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -34,6 +34,7 @@
 #include "libpq/scram.h"
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 #include "postmaster/postmaster.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -665,7 +666,7 @@ sendAuthRequest(Port *port, AuthRequest areq, const char *extradata, int extrale
 
 	CHECK_FOR_INTERRUPTS();
 
-	pq_beginmessage(&buf, 'R');
+	pq_beginmessage(&buf, PQMSG_REQ_AUTHENTICATION);
 	pq_sendint32(&buf, (int32) areq);
 	if (extralen > 0)
 		pq_sendbytes(&buf, extradata, extralen);
@@ -698,7 +699,7 @@ recv_password_packet(Port *port)
 
 	/* Expect 'p' message type */
 	mtype = pq_getbyte();
-	if (mtype != 'p')
+	if (mtype != PQMSG_RESP_PASSWORD)
 	{
 		/*
 		 * If the client just disconnects without offering a password, don't
@@ -961,7 +962,7 @@ pg_GSS_recvauth(Port *port)
 		CHECK_FOR_INTERRUPTS();
 
 		mtype = pq_getbyte();
-		if (mtype != 'p')
+		if (mtype != PQMSG_RESP_GSS)
 		{
 			/* Only log error if client didn't disconnect. */
 			if (mtype != EOF)
@@ -1232,7 +1233,7 @@ pg_SSPI_recvauth(Port *port)
 	{
 		pq_startmsgread();
 		mtype = pq_getbyte();
-		if (mtype != 'p')
+		if (mtype != PQMSG_RESP_GSS)
 		{
 			if (sspictx != NULL)
 			{
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9c8ec779f9..52c15bebb7 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -114,6 +114,7 @@
 #include "postmaster/pgarch.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
+#include "protocol.h"
 #include "replication/logicallauncher.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
@@ -2357,7 +2358,7 @@ SendNegotiateProtocolVersion(List *unrecognized_protocol_options)
 	StringInfoData buf;
 	ListCell   *lc;
 
-	pq_beginmessage(&buf, 'v'); /* NegotiateProtocolVersion */
+	pq_beginmessage(&buf, PQMSG_RESP_NEGOTIATE_PROTOCOL);
 	pq_sendint32(&buf, PG_PROTOCOL_LATEST);
 	pq_sendint32(&buf, list_length(unrecognized_protocol_options));
 	foreach(lc, unrecognized_protocol_options)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d27ef2985d..4bc2a1018c 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -69,6 +69,7 @@
 #include "nodes/replnodes.h"
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
+#include "protocol.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slot.h"
@@ -603,7 +604,7 @@ SendTimeLineHistory(TimeLineHistoryCmd *cmd)
 	dest->rStartup(dest, CMD_SELECT, tupdesc);
 
 	/* Send a DataRow message */
-	pq_beginmessage(&buf, 'D');
+	pq_beginmessage(&buf, PQMSG_RESP_DATA_ROW);
 	pq_sendint16(&buf, 2);		/* # of columns */
 	len = strlen(histfname);
 	pq_sendint32(&buf, len);	/* col1 len */
@@ -801,7 +802,7 @@ StartReplication(StartReplicationCmd *cmd)
 		WalSndSetState(WALSNDSTATE_CATCHUP);
 
 		/* Send a CopyBothResponse message, and start streaming */
-		pq_beginmessage(&buf, 'W');
+		pq_beginmessage(&buf, PQMSG_RESP_COPY_BOTH);
 		pq_sendbyte(&buf, 0);
 		pq_sendint16(&buf, 0);
 		pq_endmessage(&buf);
@@ -1294,7 +1295,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	WalSndSetState(WALSNDSTATE_CATCHUP);
 
 	/* Send a CopyBothResponse message, and start streaming */
-	pq_beginmessage(&buf, 'W');
+	pq_beginmessage(&buf, PQMSG_RESP_COPY_BOTH);
 	pq_sendbyte(&buf, 0);
 	pq_sendint16(&buf, 0);
 	pq_endmessage(&buf);
@@ -1923,11 +1924,11 @@ ProcessRepliesIfAny(void)
 		/* Validate message type and set packet size limit */
 		switch (firstchar)
 		{
-			case 'd':
+			case PQMSG_REQ_COPY_DATA:
 				maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 				break;
-			case 'c':
-			case 'X':
+			case PQMSG_REQ_COPY_DONE:
+			case PQMSG_REQ_TERMINATE:
 				maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 				break;
 			default:
@@ -1955,7 +1956,7 @@ ProcessRepliesIfAny(void)
 				/*
 				 * 'd' means a standby reply wrapped in a CopyData packet.
 				 */
-			case 'd':
+			case PQMSG_REQ_COPY_DATA:
 				ProcessStandbyMessage();
 				received = true;
 				break;
@@ -1964,7 +1965,7 @@ ProcessRepliesIfAny(void)
 				 * CopyDone means the standby requested to finish streaming.
 				 * Reply with CopyDone, if we had not sent that already.
 				 */
-			case 'c':
+			case PQMSG_REQ_COPY_DONE:
 				if (!streamingDoneSending)
 				{
 					pq_putmessage_noblock('c', NULL, 0);
@@ -1978,7 +1979,7 @@ ProcessRepliesIfAny(void)
 				/*
 				 * 'X' means that the standby is closing down the socket.
 				 */
-			case 'X':
+			case PQMSG_REQ_TERMINATE:
 				proc_exit(0);
 
 			default:
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index c0406e2ee5..a9bb80ac91 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -39,6 +39,7 @@
 #include "executor/tstoreReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
+#include "protocol.h"
 #include "utils/portal.h"
 
 
@@ -220,7 +221,7 @@ NullCommand(CommandDest dest)
 		case DestRemoteSimple:
 
 			/* Tell the FE that we saw an empty query string */
-			pq_putemptymessage('I');
+			pq_putemptymessage(PQMSG_RESP_EMPTY_QUERY);
 			break;
 
 		case DestNone:
@@ -258,7 +259,7 @@ ReadyForQuery(CommandDest dest)
 			{
 				StringInfoData buf;
 
-				pq_beginmessage(&buf, 'Z');
+				pq_beginmessage(&buf, PQMSG_RESP_READY_FOR_QUERY);
 				pq_sendbyte(&buf, TransactionBlockStatusCode());
 				pq_endmessage(&buf);
 			}
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 2f70ebd5fa..384e53a651 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -27,6 +27,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 #include "tcop/fastpath.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
@@ -69,7 +70,7 @@ SendFunctionResult(Datum retval, bool isnull, Oid rettype, int16 format)
 {
 	StringInfoData buf;
 
-	pq_beginmessage(&buf, 'V');
+	pq_beginmessage(&buf, PQMSG_RESP_FUNCTION_CALL);
 
 	if (isnull)
 	{
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 36cc99ec9c..dd8cce7a9a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -55,6 +55,7 @@
 #include "postmaster/autovacuum.h"
 #include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
+#include "protocol.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/slot.h"
@@ -402,37 +403,37 @@ SocketBackend(StringInfo inBuf)
 	 */
 	switch (qtype)
 	{
-		case 'Q':				/* simple query */
+		case PQMSG_REQ_SIMPLE_QUERY:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
 
-		case 'F':				/* fastpath function call */
+		case PQMSG_REQ_FUNCTION_CALL:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
 
-		case 'X':				/* terminate */
+		case PQMSG_REQ_TERMINATE:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			ignore_till_sync = false;
 			break;
 
-		case 'B':				/* bind */
-		case 'P':				/* parse */
+		case PQMSG_REQ_BIND :
+		case PQMSG_REQ_PARSE:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = true;
 			break;
 
-		case 'C':				/* close */
-		case 'D':				/* describe */
-		case 'E':				/* execute */
-		case 'H':				/* flush */
+		case PQMSG_REQ_CLOSE:
+		case PQMSG_REQ_DESCRIBE:
+		case PQMSG_REQ_EXECUTE:
+		case PQMSG_REQ_FLUSH_DATA:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			doing_extended_query_message = true;
 			break;
 
-		case 'S':				/* sync */
+		case PQMSG_REQ_SYNC_DATA:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			/* stop any active skip-till-Sync */
 			ignore_till_sync = false;
@@ -440,13 +441,13 @@ SocketBackend(StringInfo inBuf)
 			doing_extended_query_message = false;
 			break;
 
-		case 'd':				/* copy data */
+		case PQMSG_REQ_COPY_DATA:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
 
-		case 'c':				/* copy done */
-		case 'f':				/* copy fail */
+		case PQMSG_REQ_COPY_DONE:
+		case PQMSG_REQ_COPY_FAIL:
 			maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
 			doing_extended_query_message = false;
 			break;
@@ -1589,7 +1590,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 	 * Send ParseComplete.
 	 */
 	if (whereToSendOutput == DestRemote)
-		pq_putemptymessage('1');
+		pq_putemptymessage(PQMSG_RESP_PARSE_COMPLETE);
 
 	/*
 	 * Emit duration logging if appropriate.
@@ -2047,7 +2048,7 @@ exec_bind_message(StringInfo input_message)
 	 * Send BindComplete.
 	 */
 	if (whereToSendOutput == DestRemote)
-		pq_putemptymessage('2');
+		pq_putemptymessage(PQMSG_RESP_BIND_COMPLETE);
 
 	/*
 	 * Emit duration logging if appropriate.
@@ -2290,7 +2291,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 	{
 		/* Portal run not complete, so send PortalSuspended */
 		if (whereToSendOutput == DestRemote)
-			pq_putemptymessage('s');
+			pq_putemptymessage(PQMSG_RESP_PORTAL_SUSPENDED);
 
 		/*
 		 * Set XACT_FLAGS_PIPELINING whenever we suspend an Execute message,
@@ -2683,7 +2684,7 @@ exec_describe_statement_message(const char *stmt_name)
 								  NULL);
 	}
 	else
-		pq_putemptymessage('n');	/* NoData */
+		pq_putemptymessage(PQMSG_RESP_NO_DATA);
 }
 
 /*
@@ -2736,7 +2737,7 @@ exec_describe_portal_message(const char *portal_name)
 								  FetchPortalTargetList(portal),
 								  portal->formats);
 	else
-		pq_putemptymessage('n');	/* NoData */
+		pq_putemptymessage(PQMSG_RESP_NO_DATA);
 }
 
 
@@ -4239,7 +4240,7 @@ PostgresMain(const char *dbname, const char *username)
 	{
 		StringInfoData buf;
 
-		pq_beginmessage(&buf, 'K');
+		pq_beginmessage(&buf, PQMSG_REQ_BACKEND_KEY_DATA);
 		pq_sendint32(&buf, (int32) MyProcPid);
 		pq_sendint32(&buf, (int32) MyCancelKey);
 		pq_endmessage(&buf);
@@ -4618,7 +4619,7 @@ PostgresMain(const char *dbname, const char *username)
 
 		switch (firstchar)
 		{
-			case 'Q':			/* simple query */
+			case PQMSG_REQ_SIMPLE_QUERY:
 				{
 					const char *query_string;
 
@@ -4642,7 +4643,7 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'P':			/* parse */
+			case PQMSG_REQ_PARSE:
 				{
 					const char *stmt_name;
 					const char *query_string;
@@ -4672,7 +4673,7 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'B':			/* bind */
+			case PQMSG_REQ_BIND:
 				forbidden_in_wal_sender(firstchar);
 
 				/* Set statement_timestamp() */
@@ -4687,7 +4688,7 @@ PostgresMain(const char *dbname, const char *username)
 				/* exec_bind_message does valgrind_report_error_query */
 				break;
 
-			case 'E':			/* execute */
+			case PQMSG_REQ_EXECUTE:
 				{
 					const char *portal_name;
 					int			max_rows;
@@ -4707,7 +4708,7 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'F':			/* fastpath function call */
+			case PQMSG_REQ_FUNCTION_CALL:
 				forbidden_in_wal_sender(firstchar);
 
 				/* Set statement_timestamp() */
@@ -4742,7 +4743,7 @@ PostgresMain(const char *dbname, const char *username)
 				send_ready_for_query = true;
 				break;
 
-			case 'C':			/* close */
+			case PQMSG_REQ_CLOSE:
 				{
 					int			close_type;
 					const char *close_target;
@@ -4755,7 +4756,7 @@ PostgresMain(const char *dbname, const char *username)
 
 					switch (close_type)
 					{
-						case 'S':
+						case PREPARED_SUB_COMMAND:
 							if (close_target[0] != '\0')
 								DropPreparedStatement(close_target, false);
 							else
@@ -4764,7 +4765,7 @@ PostgresMain(const char *dbname, const char *username)
 								drop_unnamed_stmt();
 							}
 							break;
-						case 'P':
+						case PORTAL_SUB_COMMAND:
 							{
 								Portal		portal;
 
@@ -4782,13 +4783,13 @@ PostgresMain(const char *dbname, const char *username)
 					}
 
 					if (whereToSendOutput == DestRemote)
-						pq_putemptymessage('3');	/* CloseComplete */
+						pq_putemptymessage(PQMSG_RESP_CLOSE_COMPLETE);
 
 					valgrind_report_error_query("CLOSE message");
 				}
 				break;
 
-			case 'D':			/* describe */
+			case PQMSG_REQ_DESCRIBE:
 				{
 					int			describe_type;
 					const char *describe_target;
@@ -4804,10 +4805,10 @@ PostgresMain(const char *dbname, const char *username)
 
 					switch (describe_type)
 					{
-						case 'S':
+						case PREPARED_SUB_COMMAND:
 							exec_describe_statement_message(describe_target);
 							break;
-						case 'P':
+						case PORTAL_SUB_COMMAND:
 							exec_describe_portal_message(describe_target);
 							break;
 						default:
@@ -4822,13 +4823,13 @@ PostgresMain(const char *dbname, const char *username)
 				}
 				break;
 
-			case 'H':			/* flush */
+			case PQMSG_REQ_FLUSH_DATA:
 				pq_getmsgend(&input_message);
 				if (whereToSendOutput == DestRemote)
 					pq_flush();
 				break;
 
-			case 'S':			/* sync */
+			case PQMSG_REQ_SYNC_DATA:
 				pq_getmsgend(&input_message);
 				finish_xact_command();
 				valgrind_report_error_query("SYNC message");
@@ -4847,7 +4848,7 @@ PostgresMain(const char *dbname, const char *username)
 
 				/* FALLTHROUGH */
 
-			case 'X':
+			case PQMSG_REQ_TERMINATE:
 
 				/*
 				 * Reset whereToSendOutput to prevent ereport from attempting
@@ -4865,9 +4866,9 @@ PostgresMain(const char *dbname, const char *username)
 				 */
 				proc_exit(0);
 
-			case 'd':			/* copy data */
-			case 'c':			/* copy done */
-			case 'f':			/* copy fail */
+			case PQMSG_REQ_COPY_DATA:
+			case PQMSG_REQ_COPY_DONE:
+			case PQMSG_REQ_COPY_FAIL:
 
 				/*
 				 * Accept but ignore these messages, per protocol spec; we
@@ -4897,7 +4898,7 @@ forbidden_in_wal_sender(char firstchar)
 {
 	if (am_walsender)
 	{
-		if (firstchar == 'F')
+		if (firstchar == PQMSG_REQ_FUNCTION_CALL)
 			ereport(ERROR,
 					(errcode(ERRCODE_PROTOCOL_VIOLATION),
 					 errmsg("fastpath function calls not supported in a replication connection")));
diff --git a/src/backend/utils/activity/backend_progress.c b/src/backend/utils/activity/backend_progress.c
index 67447ef03a..6200433594 100644
--- a/src/backend/utils/activity/backend_progress.c
+++ b/src/backend/utils/activity/backend_progress.c
@@ -10,6 +10,7 @@
  */
 #include "postgres.h"
 
+#include "protocol.h"
 #include "access/parallel.h"
 #include "libpq/pqformat.h"
 #include "port/atomics.h"		/* for memory barriers */
@@ -102,7 +103,7 @@ pgstat_progress_parallel_incr_param(int index, int64 incr)
 
 		initStringInfo(&progress_message);
 
-		pq_beginmessage(&progress_message, 'P');
+		pq_beginmessage(&progress_message, PQMSG_RESP_PARALLEL_PROGRESS);
 		pq_sendint32(&progress_message, index);
 		pq_sendint64(&progress_message, incr);
 		pq_endmessage(&progress_message);
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 5898100acb..2e94ef096a 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -77,6 +77,7 @@
 #include "postmaster/bgworker.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
+#include "protocol.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
@@ -3465,7 +3466,7 @@ send_message_to_frontend(ErrorData *edata)
 		char		tbuf[12];
 
 		/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
-		pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
+		pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? PQMSG_RESP_NOTICE : PQMSG_RESP_ERROR);
 
 		sev = error_severity(edata->elevel);
 		pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5308896c87..1b9632942e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -37,6 +37,7 @@
 #include "libpq/pqformat.h"
 #include "parser/scansup.h"
 #include "port/pg_bitutils.h"
+#include "protocol.h"
 #include "storage/fd.h"
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
@@ -2593,7 +2594,7 @@ ReportGUCOption(struct config_generic *record)
 	{
 		StringInfoData msgbuf;
 
-		pq_beginmessage(&msgbuf, 'S');
+		pq_beginmessage(&msgbuf, PQMSG_RESP_PARAMETER_STATUS);
 		pq_sendstring(&msgbuf, record->name);
 		pq_sendstring(&msgbuf, val);
 		pq_endmessage(&msgbuf);
diff --git a/src/include/protocol.h b/src/include/protocol.h
new file mode 100644
index 0000000000..0540b36264
--- /dev/null
+++ b/src/include/protocol.h
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol.h
+ *	  Exports from postmaster/postmaster.c.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * src/include/protocol.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _PROTOCOL_H
+#define _PROTOCOL_H
+
+#define PQMSG_REQ_BIND              'B'
+#define PQMSG_REQ_CLOSE             'C'
+#define PQMSG_REQ_DESCRIBE          'D'
+#define PQMSG_REQ_EXECUTE           'E'
+#define PQMSG_REQ_FUNCTION_CALL     'F'
+#define PQMSG_REQ_FLUSH_DATA        'H'
+#define PQMSG_REQ_BACKEND_KEY_DATA  'K'
+#define PQMSG_REQ_PARSE             'P'
+#define PQMSG_REQ_AUTHENTICATION    'R'
+#define PQMSG_REQ_SYNC_DATA         'S'
+#define PQMSG_REQ_SIMPLE_QUERY      'Q'
+#define PQMSG_REQ_TERMINATE         'X'
+#define PQMSG_REQ_COPY_FAIL         'f'
+#define PQMSG_REQ_COPY_DONE         'c'
+#define PQMSG_REQ_COPY_DATA         'd'
+#define PQMSG_REQ_COPY_PROGRESS     'p'
+#define PREPARED_SUB_COMMAND        'S'
+#define PORTAL_SUB_COMMAND          'P'
+
+
+/*
+Responses
+*/
+#define PQMSG_RESP_PARSE_COMPLETE   '1'
+#define PQMSG_RESP_BIND_COMPLETE    '2'
+#define PQMSG_RESP_CLOSE_COMPLETE   '3'
+#define PQMSG_RESP_NOTIFY           'A'
+#define PQMSG_RESP_COMMAND_COMPLETE 'C'
+#define PQMSG_RESP_DATA_ROW         'D'
+#define PQMSG_RESP_ERROR            'E'
+#define PQMSG_RESP_COPY_IN          'G'
+#define PQMSG_RESP_COPY_OUT         'H'
+#define PQMSG_RESP_EMPTY_QUERY      'I'
+#define PQMSG_RESP_NOTICE           'N'
+#define PQMSG_RESP_PARALLEL_PROGRESS 'P'
+#define PQMSG_RESP_FUNCTION_CALL    'V'
+#define PQMSG_RESP_PARAMETER_STATUS 'S'
+#define PQMSG_RESP_ROW_DESCRIPTION  'T'
+#define PQMSG_RESP_COPY_BOTH        'W'
+#define PQMSG_RESP_READY_FOR_QUERY  'Z'
+#define PQMSG_RESP_NO_DATA          'n'
+#define PQMSG_RESP_PASSWORD         'p'
+#define PQMSG_RESP_GSS              'p'
+#define PQMSG_RESP_PORTAL_SUSPENDED 's'
+#define PQMSG_RESP_PARAMETER_DESCRIPTION 't'
+#define PQMSG_RESP_NEGOTIATE_PROTOCOL    'v'
+#endif
\ No newline at end of file
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 887ca5e9e1..1980e745dd 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -43,6 +43,7 @@
 #include "fe-auth.h"
 #include "fe-auth-sasl.h"
 #include "libpq-fe.h"
+#include "protocol.h"
 
 #ifdef ENABLE_GSS
 /*
@@ -586,7 +587,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
 	/*
 	 * Build a SASLInitialResponse message, and send it.
 	 */
-	if (pqPutMsgStart('p', conn))
+	if (pqPutMsgStart(PQMSG_RESP_GSS, conn))
 		goto error;
 	if (pqPuts(selected_mechanism, conn))
 		goto error;
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 837c5321aa..18f27b3cd0 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -32,6 +32,7 @@
 #include "mb/pg_wchar.h"
 #include "pg_config_paths.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 
 #ifdef WIN32
 #include "win32.h"
@@ -3591,7 +3592,7 @@ keep_going:						/* We will come back to here until there is
 				 * Anything else probably means it's not Postgres on the other
 				 * end at all.
 				 */
-				if (!(beresp == 'R' || beresp == 'v' || beresp == 'E'))
+				if (!(beresp == PQMSG_REQ_AUTHENTICATION || beresp == PQMSG_RESP_NEGOTIATE_PROTOCOL || beresp == PQMSG_RESP_ERROR))
 				{
 					libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
 											beresp);
@@ -3618,19 +3619,19 @@ keep_going:						/* We will come back to here until there is
 				 * version 14, the server also used the old protocol for
 				 * errors that happened before processing the startup packet.)
 				 */
-				if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
+				if (beresp == PQMSG_REQ_AUTHENTICATION && (msgLength < 8 || msgLength > 2000))
 				{
 					libpq_append_conn_error(conn, "received invalid authentication request");
 					goto error_return;
 				}
-				if (beresp == 'v' && (msgLength < 8 || msgLength > 2000))
+				if (beresp == PQMSG_RESP_NEGOTIATE_PROTOCOL && (msgLength < 8 || msgLength > 2000))
 				{
 					libpq_append_conn_error(conn, "received invalid protocol negotiation message");
 					goto error_return;
 				}
 
 #define MAX_ERRLEN 30000
-				if (beresp == 'E' && (msgLength < 8 || msgLength > MAX_ERRLEN))
+				if (beresp == PQMSG_RESP_ERROR && (msgLength < 8 || msgLength > MAX_ERRLEN))
 				{
 					/* Handle error from a pre-3.0 server */
 					conn->inCursor = conn->inStart + 1; /* reread data */
@@ -3693,7 +3694,7 @@ keep_going:						/* We will come back to here until there is
 				}
 
 				/* Handle errors. */
-				if (beresp == 'E')
+				if (beresp == PQMSG_RESP_ERROR)
 				{
 					if (pqGetErrorNotice3(conn, true))
 					{
@@ -3770,7 +3771,7 @@ keep_going:						/* We will come back to here until there is
 
 					goto error_return;
 				}
-				else if (beresp == 'v')
+				else if (beresp == PQMSG_RESP_NEGOTIATE_PROTOCOL)
 				{
 					if (pqGetNegotiateProtocolVersion3(conn))
 					{
@@ -4540,7 +4541,7 @@ sendTerminateConn(PGconn *conn)
 		 * Try to send "close connection" message to backend. Ignore any
 		 * error.
 		 */
-		pqPutMsgStart('X', conn);
+		pqPutMsgStart(PQMSG_REQ_TERMINATE, conn);
 		pqPutMsgEnd(conn);
 		(void) pqFlush(conn);
 	}
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index a868284ff8..e105132173 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -27,6 +27,7 @@
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
+#include "protocol.h"
 
 /* keep this in same order as ExecStatusType in libpq-fe.h */
 char	   *const pgresStatus[] = {
@@ -1458,7 +1459,7 @@ PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery)
 
 	/* Send the query message(s) */
 	/* construct the outgoing Query message */
-	if (pqPutMsgStart('Q', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_SIMPLE_QUERY, conn) < 0 ||
 		pqPuts(query, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 	{
@@ -1571,7 +1572,7 @@ PQsendPrepare(PGconn *conn,
 		return 0;				/* error msg already set */
 
 	/* construct the Parse message */
-	if (pqPutMsgStart('P', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_PARSE, conn) < 0 ||
 		pqPuts(stmtName, conn) < 0 ||
 		pqPuts(query, conn) < 0)
 		goto sendFailed;
@@ -1599,7 +1600,7 @@ PQsendPrepare(PGconn *conn,
 	/* Add a Sync, unless in pipeline mode. */
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_SYNC_DATA, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			goto sendFailed;
 	}
@@ -1784,7 +1785,7 @@ PQsendQueryGuts(PGconn *conn,
 	if (command)
 	{
 		/* construct the Parse message */
-		if (pqPutMsgStart('P', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_PARSE, conn) < 0 ||
 			pqPuts(stmtName, conn) < 0 ||
 			pqPuts(command, conn) < 0)
 			goto sendFailed;
@@ -1808,7 +1809,7 @@ PQsendQueryGuts(PGconn *conn,
 	}
 
 	/* Construct the Bind message */
-	if (pqPutMsgStart('B', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_BIND, conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPuts(stmtName, conn) < 0)
 		goto sendFailed;
@@ -1874,14 +1875,14 @@ PQsendQueryGuts(PGconn *conn,
 		goto sendFailed;
 
 	/* construct the Describe Portal message */
-	if (pqPutMsgStart('D', conn) < 0 ||
-		pqPutc('P', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_DESCRIBE, conn) < 0 ||
+		pqPutc(PORTAL_SUB_COMMAND, conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
 	/* construct the Execute message */
-	if (pqPutMsgStart('E', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_EXECUTE, conn) < 0 ||
 		pqPuts("", conn) < 0 ||
 		pqPutInt(0, 4, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
@@ -1890,7 +1891,7 @@ PQsendQueryGuts(PGconn *conn,
 	/* construct the Sync message if not in pipeline mode */
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_SYNC_DATA, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			goto sendFailed;
 	}
@@ -2422,7 +2423,7 @@ PQdescribePrepared(PGconn *conn, const char *stmt)
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendTypedCommand(conn, 'D', 'S', stmt))
+	if (!PQsendTypedCommand(conn, PQMSG_REQ_DESCRIBE, PREPARED_SUB_COMMAND, stmt))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -2441,7 +2442,7 @@ PQdescribePortal(PGconn *conn, const char *portal)
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendTypedCommand(conn, 'D', 'P', portal))
+	if (!PQsendTypedCommand(conn, PQMSG_REQ_DESCRIBE, PORTAL_SUB_COMMAND, portal))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -2456,7 +2457,7 @@ PQdescribePortal(PGconn *conn, const char *portal)
 int
 PQsendDescribePrepared(PGconn *conn, const char *stmt)
 {
-	return PQsendTypedCommand(conn, 'D', 'S', stmt);
+	return PQsendTypedCommand(conn, PQMSG_REQ_DESCRIBE, PREPARED_SUB_COMMAND, stmt);
 }
 
 /*
@@ -2469,7 +2470,7 @@ PQsendDescribePrepared(PGconn *conn, const char *stmt)
 int
 PQsendDescribePortal(PGconn *conn, const char *portal)
 {
-	return PQsendTypedCommand(conn, 'D', 'P', portal);
+	return PQsendTypedCommand(conn, PQMSG_REQ_DESCRIBE, PORTAL_SUB_COMMAND, portal);
 }
 
 /*
@@ -2488,7 +2489,7 @@ PQclosePrepared(PGconn *conn, const char *stmt)
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendTypedCommand(conn, 'C', 'S', stmt))
+	if (!PQsendTypedCommand(conn, PQMSG_REQ_CLOSE, PREPARED_SUB_COMMAND, stmt))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -2506,7 +2507,7 @@ PQclosePortal(PGconn *conn, const char *portal)
 {
 	if (!PQexecStart(conn))
 		return NULL;
-	if (!PQsendTypedCommand(conn, 'C', 'P', portal))
+	if (!PQsendTypedCommand(conn, PQMSG_REQ_CLOSE, PORTAL_SUB_COMMAND, portal))
 		return NULL;
 	return PQexecFinish(conn);
 }
@@ -2521,7 +2522,7 @@ PQclosePortal(PGconn *conn, const char *portal)
 int
 PQsendClosePrepared(PGconn *conn, const char *stmt)
 {
-	return PQsendTypedCommand(conn, 'C', 'S', stmt);
+	return PQsendTypedCommand(conn, PQMSG_REQ_CLOSE, PREPARED_SUB_COMMAND, stmt);
 }
 
 /*
@@ -2534,7 +2535,7 @@ PQsendClosePrepared(PGconn *conn, const char *stmt)
 int
 PQsendClosePortal(PGconn *conn, const char *portal)
 {
-	return PQsendTypedCommand(conn, 'C', 'P', portal);
+	return PQsendTypedCommand(conn, PQMSG_REQ_CLOSE, PORTAL_SUB_COMMAND, portal);
 }
 
 /*
@@ -2577,17 +2578,17 @@ PQsendTypedCommand(PGconn *conn, char command, char type, const char *target)
 	/* construct the Sync message */
 	if (conn->pipelineStatus == PQ_PIPELINE_OFF)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_SYNC_DATA, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			goto sendFailed;
 	}
 
 	/* remember if we are doing a Close or a Describe */
-	if (command == 'C')
+	if (command == PQMSG_REQ_CLOSE)
 	{
 		entry->queryclass = PGQUERY_CLOSE;
 	}
-	else if (command == 'D')
+	else if (command == PQMSG_REQ_DESCRIBE)
 	{
 		entry->queryclass = PGQUERY_DESCRIBE;
 	}
@@ -2696,7 +2697,7 @@ PQputCopyData(PGconn *conn, const char *buffer, int nbytes)
 				return pqIsnonblocking(conn) ? 0 : -1;
 		}
 		/* Send the data (too simple to delegate to fe-protocol files) */
-		if (pqPutMsgStart('d', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_COPY_DATA, conn) < 0 ||
 			pqPutnchar(buffer, nbytes, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
@@ -2731,7 +2732,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (errormsg)
 	{
 		/* Send COPY FAIL */
-		if (pqPutMsgStart('f', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_COPY_FAIL, conn) < 0 ||
 			pqPuts(errormsg, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
@@ -2739,7 +2740,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	else
 	{
 		/* Send COPY DONE */
-		if (pqPutMsgStart('c', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_COPY_DONE, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
 	}
@@ -2751,7 +2752,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 	if (conn->cmd_queue_head &&
 		conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE)
 	{
-		if (pqPutMsgStart('S', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_SYNC_DATA, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return -1;
 	}
@@ -3263,7 +3264,7 @@ PQpipelineSync(PGconn *conn)
 	entry->query = NULL;
 
 	/* construct the Sync message */
-	if (pqPutMsgStart('S', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_SYNC_DATA, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 		goto sendFailed;
 
@@ -3311,7 +3312,7 @@ PQsendFlushRequest(PGconn *conn)
 		return 0;
 	}
 
-	if (pqPutMsgStart('H', conn) < 0 ||
+	if (pqPutMsgStart(PQMSG_REQ_FLUSH_DATA, conn) < 0 ||
 		pqPutMsgEnd(conn) < 0)
 	{
 		return 0;
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 7bc6355d17..8de12f4aca 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -28,14 +28,16 @@
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 
 /*
  * This macro lists the backend message types that could be "long" (more
  * than a couple of kilobytes).
  */
 #define VALID_LONG_MESSAGE_TYPE(id) \
-	((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \
-	 (id) == 'E' || (id) == 'N' || (id) == 'A')
+	((id) == PQMSG_RESP_ROW_DESCRIPTION || (id) == PQMSG_RESP_DATA_ROW || (id) == PQMSG_REQ_COPY_DATA || \
+	 (id) == PQMSG_RESP_FUNCTION_CALL ||  (id) == PQMSG_RESP_ERROR || (id) == PQMSG_RESP_NOTICE || \
+	 (id) == PQMSG_RESP_NOTIFY)
 
 
 static void handleSyncLoss(PGconn *conn, char id, int msgLength);
@@ -140,12 +142,12 @@ pqParseInput3(PGconn *conn)
 		 * from config file due to SIGHUP), but otherwise we hold off until
 		 * BUSY state.
 		 */
-		if (id == 'A')
+		if (id == PQMSG_RESP_NOTIFY)
 		{
 			if (getNotify(conn))
 				return;
 		}
-		else if (id == 'N')
+		else if (id == PQMSG_RESP_NOTICE)
 		{
 			if (pqGetErrorNotice3(conn, false))
 				return;
@@ -165,12 +167,12 @@ pqParseInput3(PGconn *conn)
 			 * it is about to close the connection, so we don't want to just
 			 * discard it...)
 			 */
-			if (id == 'E')
+			if (id == PQMSG_RESP_ERROR)
 			{
 				if (pqGetErrorNotice3(conn, false /* treat as notice */ ))
 					return;
 			}
-			else if (id == 'S')
+			else if (id == PQMSG_RESP_PARAMETER_STATUS)
 			{
 				if (getParameterStatus(conn))
 					return;
@@ -192,7 +194,7 @@ pqParseInput3(PGconn *conn)
 			 */
 			switch (id)
 			{
-				case 'C':		/* command complete */
+				case PQMSG_RESP_COMMAND_COMPLETE:
 					if (pqGets(&conn->workBuffer, conn))
 						return;
 					if (!pgHavePendingResult(conn))
@@ -210,12 +212,12 @@ pqParseInput3(PGconn *conn)
 								CMDSTATUS_LEN);
 					conn->asyncStatus = PGASYNC_READY;
 					break;
-				case 'E':		/* error return */
+				case PQMSG_RESP_ERROR:
 					if (pqGetErrorNotice3(conn, true))
 						return;
 					conn->asyncStatus = PGASYNC_READY;
 					break;
-				case 'Z':		/* sync response, backend is ready for new
+				case PQMSG_RESP_READY_FOR_QUERY:		/* sync response, backend is ready for new
 								 * query */
 					if (getReadyForQuery(conn))
 						return;
@@ -246,7 +248,7 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_IDLE;
 					}
 					break;
-				case 'I':		/* empty query */
+				case PQMSG_RESP_EMPTY_QUERY:
 					if (!pgHavePendingResult(conn))
 					{
 						conn->result = PQmakeEmptyPGresult(conn,
@@ -259,7 +261,7 @@ pqParseInput3(PGconn *conn)
 					}
 					conn->asyncStatus = PGASYNC_READY;
 					break;
-				case '1':		/* Parse Complete */
+				case PQMSG_RESP_PARSE_COMPLETE:
 					/* If we're doing PQprepare, we're done; else ignore */
 					if (conn->cmd_queue_head &&
 						conn->cmd_queue_head->queryclass == PGQUERY_PREPARE)
@@ -277,10 +279,10 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_READY;
 					}
 					break;
-				case '2':		/* Bind Complete */
+				case PQMSG_RESP_BIND_COMPLETE:
 					/* Nothing to do for this message type */
 					break;
-				case '3':		/* Close Complete */
+				case PQMSG_RESP_CLOSE_COMPLETE:
 					/* If we're doing PQsendClose, we're done; else ignore */
 					if (conn->cmd_queue_head &&
 						conn->cmd_queue_head->queryclass == PGQUERY_CLOSE)
@@ -298,11 +300,11 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_READY;
 					}
 					break;
-				case 'S':		/* parameter status */
+				case PQMSG_RESP_PARAMETER_STATUS:
 					if (getParameterStatus(conn))
 						return;
 					break;
-				case 'K':		/* secret key data from the backend */
+				case PQMSG_REQ_BACKEND_KEY_DATA:		/* secret key data from the backend */
 
 					/*
 					 * This is expected only during backend startup, but it's
@@ -314,7 +316,7 @@ pqParseInput3(PGconn *conn)
 					if (pqGetInt(&(conn->be_key), 4, conn))
 						return;
 					break;
-				case 'T':		/* Row Description */
+				case PQMSG_RESP_ROW_DESCRIPTION:
 					if (conn->error_result ||
 						(conn->result != NULL &&
 						 conn->result->resultStatus == PGRES_FATAL_ERROR))
@@ -346,7 +348,7 @@ pqParseInput3(PGconn *conn)
 						return;
 					}
 					break;
-				case 'n':		/* No Data */
+				case PQMSG_RESP_NO_DATA:
 
 					/*
 					 * NoData indicates that we will not be seeing a
@@ -374,11 +376,11 @@ pqParseInput3(PGconn *conn)
 						conn->asyncStatus = PGASYNC_READY;
 					}
 					break;
-				case 't':		/* Parameter Description */
+				case PQMSG_RESP_PARAMETER_DESCRIPTION:
 					if (getParamDescriptions(conn, msgLength))
 						return;
 					break;
-				case 'D':		/* Data Row */
+				case PQMSG_RESP_DATA_ROW:
 					if (conn->result != NULL &&
 						conn->result->resultStatus == PGRES_TUPLES_OK)
 					{
@@ -405,24 +407,24 @@ pqParseInput3(PGconn *conn)
 						conn->inCursor += msgLength;
 					}
 					break;
-				case 'G':		/* Start Copy In */
+				case PQMSG_RESP_COPY_IN:
 					if (getCopyStart(conn, PGRES_COPY_IN))
 						return;
 					conn->asyncStatus = PGASYNC_COPY_IN;
 					break;
-				case 'H':		/* Start Copy Out */
+				case PQMSG_RESP_COPY_OUT:
 					if (getCopyStart(conn, PGRES_COPY_OUT))
 						return;
 					conn->asyncStatus = PGASYNC_COPY_OUT;
 					conn->copy_already_done = 0;
 					break;
-				case 'W':		/* Start Copy Both */
+				case PQMSG_RESP_COPY_BOTH:
 					if (getCopyStart(conn, PGRES_COPY_BOTH))
 						return;
 					conn->asyncStatus = PGASYNC_COPY_BOTH;
 					conn->copy_already_done = 0;
 					break;
-				case 'd':		/* Copy Data */
+				case PQMSG_REQ_COPY_DATA:
 
 					/*
 					 * If we see Copy Data, just silently drop it.  This would
@@ -431,7 +433,7 @@ pqParseInput3(PGconn *conn)
 					 */
 					conn->inCursor += msgLength;
 					break;
-				case 'c':		/* Copy Done */
+				case PQMSG_REQ_COPY_DONE:
 
 					/*
 					 * If we see Copy Done, just silently drop it.  This is
@@ -1692,21 +1694,21 @@ getCopyDataMessage(PGconn *conn)
 		 */
 		switch (id)
 		{
-			case 'A':			/* NOTIFY */
+			case PQMSG_RESP_NOTIFY:
 				if (getNotify(conn))
 					return 0;
 				break;
-			case 'N':			/* NOTICE */
+			case PQMSG_RESP_NOTICE:
 				if (pqGetErrorNotice3(conn, false))
 					return 0;
 				break;
-			case 'S':			/* ParameterStatus */
+			case PQMSG_RESP_PARAMETER_STATUS:
 				if (getParameterStatus(conn))
 					return 0;
 				break;
-			case 'd':			/* Copy Data, pass it back to caller */
+			case PQMSG_REQ_COPY_DATA:			/* Copy Data, pass it back to caller */
 				return msgLength;
-			case 'c':
+			case PQMSG_REQ_COPY_DONE:
 
 				/*
 				 * If this is a CopyDone message, exit COPY_OUT mode and let
@@ -1929,7 +1931,7 @@ pqEndcopy3(PGconn *conn)
 	if (conn->asyncStatus == PGASYNC_COPY_IN ||
 		conn->asyncStatus == PGASYNC_COPY_BOTH)
 	{
-		if (pqPutMsgStart('c', conn) < 0 ||
+		if (pqPutMsgStart(PQMSG_REQ_COPY_DONE, conn) < 0 ||
 			pqPutMsgEnd(conn) < 0)
 			return 1;
 
@@ -1940,7 +1942,7 @@ pqEndcopy3(PGconn *conn)
 		if (conn->cmd_queue_head &&
 			conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE)
 		{
-			if (pqPutMsgStart('S', conn) < 0 ||
+			if (pqPutMsgStart(PQMSG_REQ_SYNC_DATA, conn) < 0 ||
 				pqPutMsgEnd(conn) < 0)
 				return 1;
 		}
@@ -2023,7 +2025,7 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
 
 	/* PQfn already validated connection state */
 
-	if (pqPutMsgStart('F', conn) < 0 || /* function call msg */
+	if (pqPutMsgStart(PQMSG_REQ_FUNCTION_CALL, conn) < 0 || /* function call msg */
 		pqPutInt(fnid, 4, conn) < 0 ||	/* function id */
 		pqPutInt(1, 2, conn) < 0 || /* # of format codes */
 		pqPutInt(1, 2, conn) < 0 || /* format code: BINARY */
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index 402784f40e..83c3328444 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -28,6 +28,7 @@
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "port/pg_bswap.h"
+#include "protocol.h"
 
 
 /* Enable tracing */
@@ -562,110 +563,110 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 
 	switch (id)
 	{
-		case '1':
+		case PQMSG_RESP_PARSE_COMPLETE:
 			fprintf(conn->Pfdebug, "ParseComplete");
 			/* No message content */
 			break;
-		case '2':
+		case PQMSG_RESP_BIND_COMPLETE:
 			fprintf(conn->Pfdebug, "BindComplete");
 			/* No message content */
 			break;
-		case '3':
+		case PQMSG_RESP_CLOSE_COMPLETE:
 			fprintf(conn->Pfdebug, "CloseComplete");
 			/* No message content */
 			break;
-		case 'A':				/* Notification Response */
+		case PQMSG_RESP_NOTIFY:
 			pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'B':				/* Bind */
+		case PQMSG_REQ_BIND:
 			pqTraceOutputB(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'c':
+		case PQMSG_REQ_COPY_DONE:
 			fprintf(conn->Pfdebug, "CopyDone");
 			/* No message content */
 			break;
-		case 'C':				/* Close(F) or Command Complete(B) */
+		case PQMSG_RESP_COMMAND_COMPLETE:				/* Close(F) or Command Complete(B) */
 			pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor);
 			break;
-		case 'd':				/* Copy Data */
+		case PQMSG_REQ_COPY_DATA:
 			/* Drop COPY data to reduce the overhead of logging. */
 			break;
-		case 'D':				/* Describe(F) or Data Row(B) */
+		case PQMSG_REQ_DESCRIBE:		/* Describe(F) or Data Row(B) */
 			pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor);
 			break;
-		case 'E':				/* Execute(F) or Error Response(B) */
+		case PQMSG_REQ_EXECUTE:		/* Execute(F) or Error Response(B) */
 			pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor,
 						   regress);
 			break;
-		case 'f':				/* Copy Fail */
+		case PQMSG_REQ_COPY_FAIL:
 			pqTraceOutputf(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'F':				/* Function Call */
+		case PQMSG_REQ_FUNCTION_CALL:
 			pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'G':				/* Start Copy In */
+		case PQMSG_RESP_COPY_IN:
 			pqTraceOutputG(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'H':				/* Flush(F) or Start Copy Out(B) */
+		case PQMSG_REQ_FLUSH_DATA:	/* Flush(F) or Start Copy Out(B) */
 			if (!toServer)
 				pqTraceOutputH(conn->Pfdebug, message, &logCursor);
 			else
 				fprintf(conn->Pfdebug, "Flush");	/* no message content */
 			break;
-		case 'I':
+		case PQMSG_RESP_EMPTY_QUERY:
 			fprintf(conn->Pfdebug, "EmptyQueryResponse");
 			/* No message content */
 			break;
-		case 'K':				/* secret key data from the backend */
+		case PQMSG_REQ_BACKEND_KEY_DATA:		/* secret key data from the backend */
 			pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'n':
+		case PQMSG_RESP_NO_DATA:
 			fprintf(conn->Pfdebug, "NoData");
 			/* No message content */
 			break;
-		case 'N':
+		case PQMSG_RESP_NOTICE:
 			pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message,
 							&logCursor, regress);
 			break;
-		case 'P':				/* Parse */
+		case PQMSG_REQ_PARSE:
 			pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'Q':				/* Query */
+		case PQMSG_REQ_SIMPLE_QUERY:
 			pqTraceOutputQ(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'R':				/* Authentication */
+		case PQMSG_REQ_AUTHENTICATION:
 			pqTraceOutputR(conn->Pfdebug, message, &logCursor);
 			break;
-		case 's':
+		case PQMSG_RESP_PORTAL_SUSPENDED:
 			fprintf(conn->Pfdebug, "PortalSuspended");
 			/* No message content */
 			break;
-		case 'S':				/* Parameter Status(B) or Sync(F) */
+		case PQMSG_REQ_SYNC_DATA:	/* Parameter Status(B) or Sync(F) */
 			if (!toServer)
 				pqTraceOutputS(conn->Pfdebug, message, &logCursor);
 			else
 				fprintf(conn->Pfdebug, "Sync"); /* no message content */
 			break;
-		case 't':				/* Parameter Description */
+		case PQMSG_RESP_PARAMETER_DESCRIPTION:
 			pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'T':				/* Row Description */
+		case PQMSG_RESP_ROW_DESCRIPTION:
 			pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);
 			break;
-		case 'v':				/* Negotiate Protocol Version */
+		case PQMSG_RESP_NEGOTIATE_PROTOCOL:
 			pqTraceOutputv(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'V':				/* Function Call response */
+		case PQMSG_RESP_FUNCTION_CALL:
 			pqTraceOutputV(conn->Pfdebug, message, &logCursor);
 			break;
-		case 'W':				/* Start Copy Both */
+		case PQMSG_RESP_COPY_BOTH:
 			pqTraceOutputW(conn->Pfdebug, message, &logCursor, length);
 			break;
-		case 'X':
+		case PQMSG_REQ_TERMINATE:
 			fprintf(conn->Pfdebug, "Terminate");
 			/* No message content */
 			break;
-		case 'Z':				/* Ready For Query */
+		case PQMSG_RESP_READY_FOR_QUERY:
 			pqTraceOutputZ(conn->Pfdebug, message, &logCursor);
 			break;
 		default:
-- 
2.37.1 (Apple Git-137.1)



^ permalink  raw  reply  [nested|flat] 80+ messages in thread

* Re: Using defines for protocol characters
@ 2023-08-04 06:42  Tatsuo Ishii <[email protected]>
  parent: Dave Cramer <[email protected]>
  0 siblings, 0 replies; 80+ messages in thread

From: Tatsuo Ishii @ 2023-08-04 06:42 UTC (permalink / raw)
  To: [email protected]; +Cc: [email protected]; [email protected]

> On Thu, 3 Aug 2023 at 16:59, Tatsuo Ishii <[email protected]> wrote:
> 
>> > On Thu, 3 Aug 2023 at 13:25, Tatsuo Ishii <[email protected]> wrote:
>> >
>> >> > Greetings,
>> >> >
>> >> > Attached is a patch which introduces a file protocol.h. Instead of
>> using
>> >> > the actual characters everywhere in the code this patch names the
>> >> > characters and removes the comments beside each usage.
>> >>
>> >> > +#define DESCRIBE_PREPARED           'S'
>> >> > +#define DESCRIBE_PORTAL             'P'
>> >>
>> >> You use these for Close message as well. I don't like the idea because
>> >> Close is different message from Describe message.
>> >>
>> >> What about adding following for Close too use them instead?
>> >>
>> >> #define CLOSE_PREPARED           'S'
>> >> #define CLOSE_PORTAL             'P'
>> >>
>> >
>> > Good catch.
>> > I recall when writing this it was a bit hacky.
>> > What do you think of PREPARED_SUB_COMMAND   and PORTAL_SUB_COMMAND
>> instead
>> > of duplicating them ?
>>
>> Nice. Looks good to me.
>>
> New patch attached which uses  PREPARED_SUB_COMMAND   and
> PORTAL_SUB_COMMAND instead
> and uses
> PQMSG_REQ_* and  PQMSG_RESP_*  as per Alvaro's suggestion

Looks good to me.

Best reagards,
--
Tatsuo Ishii
SRA OSS LLC
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp






^ permalink  raw  reply  [nested|flat] 80+ messages in thread


end of thread, other threads:[~2023-08-04 06:42 UTC | newest]

Thread overview: 80+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-08-03 18:01 Tightly packing expressions Douglas Doole <[email protected]>
2017-08-03 18:14 ` Andres Freund <[email protected]>
2019-02-04 16:43 [PATCH 2/2] Propagate replica identity to partitions Alvaro Herrera <[email protected]>
2019-02-04 16:43 [PATCH v3 2/2] Propagate replica identity to partitions Alvaro Herrera <[email protected]>
2019-02-04 16:43 [PATCH v2 2/2] Propagate replica identity to partitions Alvaro Herrera <[email protected]>
2020-03-06 23:23 [PATCH v9 09/11] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v37 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v24 07/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v34 06/15] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v25 07/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v26 07/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v22 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v30 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v21 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v36 6/7] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v31 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v14 7/8] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v32 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v33 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v11 8/9] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v18 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v12 10/11] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v28 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v10 8/9] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v19 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v13 7/8] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v35 6/7] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v23 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v20 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v27 06/11] pg_ls_logdir to ignore error if initial/top dir is missing.. Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v17 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2020-03-06 23:23 [PATCH v16 08/10] pg_ls_logdir to ignore error if initial/top dir is missing Justin Pryzby <[email protected]>
2023-08-03 17:40 Using defines for protocol characters Dave Cramer <[email protected]>
2023-08-03 17:59 ` Re: Using defines for protocol characters Alvaro Herrera <[email protected]>
2023-08-03 18:07   ` Re: Using defines for protocol characters Dave Cramer <[email protected]>
2023-08-03 18:53     ` Re: Using defines for protocol characters Nathan Bossart <[email protected]>
2023-08-03 19:25 ` Re: Using defines for protocol characters Tatsuo Ishii <[email protected]>
2023-08-03 21:22   ` Re: Using defines for protocol characters Dave Cramer <[email protected]>
2023-08-03 21:42     ` Re: Using defines for protocol characters Dave Cramer <[email protected]>
2023-08-03 22:59     ` Re: Using defines for protocol characters Tatsuo Ishii <[email protected]>
2023-08-04 02:09       ` Re: Using defines for protocol characters Dave Cramer <[email protected]>
2023-08-04 06:42         ` Re: Using defines for protocol characters Tatsuo Ishii <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox