diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 0e47d9a0de1..5d44644bdf5 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1668,6 +1668,8 @@ perform_pruning_base_step(PartitionPruneContext *context,
 	Datum		values[PARTITION_MAX_KEYS];
 	FmgrInfo	partsupfunc[PARTITION_MAX_KEYS];
 
+	Assert(list_length(opstep->exprs) == list_length(opstep->cmpfns));
+
 	nvalues = 0;
 	lc1 = list_head(opstep->exprs);
 	lc2 = list_head(opstep->cmpfns);
@@ -1708,8 +1710,6 @@ perform_pruning_base_step(PartitionPruneContext *context,
 				 * than the one cached in the PartitionKey, we'll need to
 				 * look up the FmgrInfo.
 				 */
-				if (lc2 == NULL)
-					elog(ERROR, "incomplete cmpfnids list in pruning step");
 				cmpfn = lfirst_oid(lc2);
 				Assert(OidIsValid(cmpfn));
 				if (cmpfn != context->partsupfunc[keyno].fn_oid)
@@ -1757,6 +1757,8 @@ perform_pruning_base_step(PartitionPruneContext *context,
 /*
  * perform_pruning_base_step_ne
  *		Returns indexes of partitions obtained by executing 'nestep'.
+ *
+ * Note this pruning method is only supported by LIST partitioning.
  */
 static Bitmapset *
 perform_pruning_base_step_ne(PartitionPruneContext *context,
@@ -1771,6 +1773,9 @@ perform_pruning_base_step_ne(PartitionPruneContext *context,
 	int			n_ne_datums = list_length(nestep->exprs),
 				i;
 
+	Assert(context->strategy == PARTITION_STRATEGY_LIST);
+	Assert(list_length(nestep->exprs) == list_length(nestep->cmpfns));
+
 	/*
 	 * Apply not-equal clauses.  This only applies in the list
 	 * partitioning case as this is the only partition type where
@@ -1783,8 +1788,7 @@ perform_pruning_base_step_ne(PartitionPruneContext *context,
 	 * Some datums may require different comparison function than
 	 * the default partitioning-specific one.
 	 */
-	partsupfunc = (FmgrInfo **)
-	palloc(n_ne_datums * sizeof(FmgrInfo *));
+	partsupfunc = (FmgrInfo **) palloc(n_ne_datums * sizeof(FmgrInfo *));
 
 	i = 0;
 	forboth(lc1, nestep->exprs, lc2, nestep->cmpfns)
@@ -1811,22 +1815,21 @@ perform_pruning_base_step_ne(PartitionPruneContext *context,
 			}
 			else
 				partsupfunc[i] = &context->partsupfunc[0];
-		}
 
-		ne_datums[i++] = datum;
+			ne_datums[i++] = datum;
+		}
 	}
 
 	excluded_parts = get_partitions_excluded_by_ne_datums(context, ne_datums, i,
 													  partsupfunc);
 	pfree(ne_datums);
+	pfree(partsupfunc);
 
 	/* All partitions apart from those in excluded_parts match */
 	result = bms_add_range(NULL, 0, context->nparts - 1);
 	return bms_del_members(result, excluded_parts);
 }
 
-
-
 /*
  * perform_pruning_combine_step
  *		Returns indexes of partitions obtained by executing 'cstep'.
@@ -1839,12 +1842,12 @@ perform_pruning_combine_step(PartitionPruneContext *context,
 	ListCell   *lc;
 
 	/*
-	 * In some cases, planner generates a combine step that doesn't contain
+	 * In some cases the planner generates a combine step that doesn't contain
 	 * any argument steps, to signal us to not prune any partitions.  So,
 	 * return all partitions in that case.
 	 */
 	if (cstep->argsteps == NIL)
-			return bms_add_range(NULL, 0, context->nparts - 1);
+		return bms_add_range(NULL, 0, context->nparts - 1);
 
 	switch (cstep->combineOp)
 	{
@@ -1863,7 +1866,10 @@ perform_pruning_combine_step(PartitionPruneContext *context,
 					 */
 					argresult = perform_pruning_step(context, step);
 
-					/* Add argresult to result. */
+					/*
+					 * Accumulate the matching partitions, effectively
+					 * bitwise-OR.
+					 */
 					result = bms_add_members(result, argresult);
 				}
 
@@ -1876,6 +1882,11 @@ perform_pruning_combine_step(PartitionPruneContext *context,
 
 				firststep = true;
 				result = NULL;
+
+				/*
+				 * Determine the partitions which are common to each step
+				 * using bitwise-AND.
+				 */
 				foreach(lc, cstep->argsteps)
 				{
 					PartitionPruneStep *step = lfirst(lc);
@@ -1883,6 +1894,11 @@ perform_pruning_combine_step(PartitionPruneContext *context,
 
 					argresult = perform_pruning_step(context, step);
 
+					/*
+					 * For the first step we take the entire result so that
+					 * we have something to bitwise-AND to on subsequent
+					 * steps.
+					 */
 					if (firststep)
 					{
 						result = argresult;
@@ -2236,7 +2252,7 @@ get_partitions_for_keys_range(PartitionPruneContext *context,
 			{
 				if (nvalues == partnatts)
 				{
-					/* There can only be one partition. */
+					/* There can only be zero or one matching partitions. */
 					if (partindices[off + 1] >= 0)
 						return bms_make_singleton(partindices[off + 1]);
 					else if (partition_bound_has_default(boundinfo))
@@ -2491,7 +2507,8 @@ get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
 
 	/*
 	 * We can only do this exclusion for list partitions because that's the
-	 * only case where we require all values to explicitly specified.
+	 * only case where we require all values to explicitly specified in the
+	 * partition boundinfo.
 	 */
 	Assert(context->strategy == PARTITION_STRATEGY_LIST);
 	Assert(context->partnatts == 1);
@@ -2525,6 +2542,12 @@ get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
 	 * counted all this, we can eliminate any partition where the number of
 	 * datums found in the query matches the number of datums allowed in the
 	 * partition.
+	 *
+	 * It might seem like we can skip attempting pruning any partitions here
+	 * if we found no not-equal clauses which match any partitions above, but
+	 * because we previously ensured these clauses are strict we may be able
+	 * to at least eliminate the NULL partition, providing that partition does
+	 * not also allow any non-NULL values.
 	 */
 	if (!bms_is_empty(foundoffsets))
 	{
@@ -2535,6 +2558,13 @@ get_partitions_excluded_by_ne_datums(PartitionPruneContext *context,
 			datums_in_query[boundinfo->indexes[i]]++;
 	}
 
+	/*
+	 * We can't prune anything if there's no foundoffsets and no NULL
+	 * partition.
+	 */
+	else if (!partition_bound_accepts_nulls(boundinfo))
+		return NULL;
+
 	/*
 	 * Now, in a single pass over all the datums, count the number of datums
 	 * permitted in each partition.
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5b765be0114..2053a114f4e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2158,6 +2158,7 @@ _copyPartitionPruneStepOpNe(const PartitionPruneStepOpNe *from)
 	PartitionPruneStepOpNe *newnode = makeNode(PartitionPruneStepOpNe);
 
 	COPY_NODE_FIELD(exprs);
+	COPY_NODE_FIELD(cmpfns);
 
 	return newnode;
 }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f733075527b..24592e2205e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1481,8 +1481,9 @@ inheritance_planner(PlannerInfo *root)
 			continue;
 
 		/* Add the current parent's RT index to the partitioned rels set. */
-		partitioned_relids = bms_add_member(partitioned_relids,
-											appinfo->parent_relid);
+		if (partitioned_relids)
+			partitioned_relids = bms_add_member(partitioned_relids,
+												appinfo->parent_relid);
 
 		/*
 		 * If this is the first non-excluded child, its post-planning rtable
diff --git a/src/backend/optimizer/util/partprune.c b/src/backend/optimizer/util/partprune.c
index 7c00d62a56a..92ca13f6936 100644
--- a/src/backend/optimizer/util/partprune.c
+++ b/src/backend/optimizer/util/partprune.c
@@ -197,7 +197,7 @@ generate_partition_pruning_steps(RelOptInfo *rel, List *clauses,
  * From OpExpr clauses that are mutually AND'd, we find combinations of those
  * that match to the partition key columns and for every such combination,
  * we emit a PartitionPruneStepOp containing a vector of expressions whose
- * values to be used as a look up key to search partitions with.  Relevant
+ * values are used as a look up key to search for partitions with.  Relevant
  * details of the operator and a vector of (possibly cross-type) comparison
  * functions is also included with each step.
  *
@@ -622,8 +622,8 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, List *clauses,
 		}
 
 		/*
-		 * If we've decided that clauses for subsequent partition keys would't
-		 * be useful for pruning, don't look.
+		 * If we've decided that clauses for subsequent partition keys
+		 * wouldn't be useful for pruning, don't look.
 		 */
 		if (!consider_next_key)
 			break;
@@ -759,9 +759,9 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, List *clauses,
 					ListCell *lc1;
 
 					/*
-					 * Locate the clause for the greatest column (whic may not
-					 * be the last partition key column).  Actually, the last
-					 * element of eq_clauses must give us what we need.
+					 * Locate the clause for the greatest column (which may
+					 * not be the last partition key column).  Actually, the
+					 * last element of eq_clauses must give us what we need.
 					 */
 					pc = llast(eq_clauses);
 
@@ -857,7 +857,7 @@ generate_partition_pruning_steps_internal(RelOptInfo *rel, List *clauses,
  *
  * Return value:
  *
- *	one of PARTCLAUSE_MATCH_* enum values if the clause is successfully
+ *	One of PARTCLAUSE_MATCH_* enum values if the clause is successfully
  *	matched to the partition key.  If it is PARTCLAUSE_MATCH_CONTRADICT, then
  *	this means the clause is self-contradictory (which can happen only if it's
  *	a BoolExpr whose arguments may be self-contradictory)
@@ -927,6 +927,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		Oid			commutator = InvalidOid,
 					negator = InvalidOid;
 		Oid			cmpfn;
+		Oid			exprtype;
 
 		leftop = (Expr *) get_leftop(clause);
 		if (IsA(leftop, RelabelType))
@@ -1014,7 +1015,8 @@ match_clause_to_partition_key(RelOptInfo *rel,
 		}
 
 		/* Check if we're going to need a cross-type comparison function. */
-		if (exprType((Node *) expr) != part_scheme->partopcintype[partkeyidx])
+		exprtype = exprType((Node *) expr);
+		if (exprtype != part_scheme->partopcintype[partkeyidx])
 		{
 			int	procnum = (part_scheme->strategy == PARTITION_STRATEGY_HASH)
 							? HASHEXTENDED_PROC
@@ -1022,7 +1024,7 @@ match_clause_to_partition_key(RelOptInfo *rel,
 
 			cmpfn = get_opfamily_proc(part_scheme->partopfamily[partkeyidx],
 									  part_scheme->partopcintype[partkeyidx],
-									  exprType((Node *) expr), procnum);
+									  exprtype, procnum);
 			/* If we couldn't find one, we cannot use this expression. */
 			if (!OidIsValid(cmpfn))
 				return PARTCLAUSE_UNSUPPORTED;
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 5a9b12b1414..400e79bd4f8 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1524,10 +1524,10 @@ typedef struct PartitionPruneStep
  * where partnatts is the number of partition key columns.  'opstrategy' is the
  * strategy of the operator in the clause matched to the last partition key.
  * 'exprs' contains expressions which comprise the look-up key to be passed to
- * the partition bound search function.  'cmpfnids' contains the OIDs of
+ * the partition bound search function.  'cmpfns' contains the OIDs of
  * comparison function used to compare aforementioned expressions with
- * partition bounds.  Both 'exprs' and 'cmpfns' contain up to partnatts
- * elements.
+ * partition bounds.  Both 'exprs' and 'cmpfns' contain the same number of
+ * items up to partnatts items.
  *
  * Once we find the offset of a partition bound using the look-up key, we
  * determine which partitions to include in the result based on the value of
@@ -1559,9 +1559,9 @@ typedef struct PartitionPruneStepOp
  *							OpExpr clauses each containing a <> operator
  *
  * This is a special form of PartitionPruneStepOp, where each of the
- * expressions in 'expr' is compared using a <> operator.  To prune a given
+ * expressions in 'exprs' is compared using a <> operator.  To prune a given
  * partition, we must check if each of the values it allows matches the value
- * of one of the expressions in 'expr' using the corresponding comparison
+ * of one of the expressions in 'exprs' using the corresponding comparison
  * function in 'cmpfns'.
  *
  * Note: Since we must consider every possible value of the partition key a
