diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 028e8ac..28fd15d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -16553,13 +16553,6 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
 				 errmsg("unrecognized partitioning strategy \"%s\"",
 						partspec->strategy)));
 
-	/* Check valid number of columns for strategy */
-	if (*strategy == PARTITION_STRATEGY_LIST &&
-		list_length(partspec->partParams) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-				 errmsg("cannot use \"list\" partition strategy with more than one column")));
-
 	/*
 	 * Create a dummy ParseState and insert the target relation as its sole
 	 * rangetable entry.  We need a ParseState for transformExpr.
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 606c920..218054a 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -1265,19 +1265,15 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull)
 			break;
 
 		case PARTITION_STRATEGY_LIST:
-			if (isnull[0])
-			{
-				if (partition_bound_accepts_nulls(boundinfo))
-					part_index = boundinfo->null_index;
-			}
-			else
 			{
 				bool		equal = false;
 
 				bound_offset = partition_list_bsearch(key->partsupfunc,
 													  key->partcollation,
 													  boundinfo,
-													  values[0], &equal);
+													  key->partnatts,
+													  values, isnull,
+													  &equal);
 				if (bound_offset >= 0 && equal)
 					part_index = boundinfo->indexes[bound_offset];
 			}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index d5b67d4..d3ece78 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -146,6 +146,9 @@ static void validateInfiniteBounds(ParseState *pstate, List *blist);
 static Const *transformPartitionBoundValue(ParseState *pstate, Node *con,
 										   const char *colName, Oid colType, int32 colTypmod,
 										   Oid partCollation);
+static List *transformPartitionListBounds(ParseState *pstate,
+										  PartitionBoundSpec *spec,
+										  Relation parent);
 
 
 /*
@@ -4017,6 +4020,42 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
 }
 
 /*
+ * checkForDuplicates
+ *
+ * Return TRUE if the list bound element is already present in the list of list
+ * bounds, FALSE otherwise.
+ */
+static bool
+checkForDuplicates(List *source, List *searchElem)
+{
+	ListCell   *cell = NULL;
+
+	foreach(cell, source)
+	{
+		int		i = 0;
+		List   *elem = lfirst(cell);
+		bool	isDuplicate	= true;
+
+		for (i = 0; i < list_length(elem); i++)
+		{
+			Const   *value1 = castNode(Const, list_nth(elem, i));
+			Const   *value2 = castNode(Const, list_nth(searchElem, i));
+
+			if (!equal(value1, value2))
+			{
+				isDuplicate = false;
+				break;
+			}
+		}
+
+		if (isDuplicate)
+			return true;
+	}
+
+	return false;
+}
+
+/*
  * transformPartitionBound
  *
  * Transform a partition bound specification
@@ -4029,7 +4068,6 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	PartitionKey key = RelationGetPartitionKey(parent);
 	char		strategy = get_partition_strategy(key);
 	int			partnatts = get_partition_natts(key);
-	List	   *partexprs = get_partition_exprs(key);
 
 	/* Avoid scribbling on input */
 	result_spec = copyObject(spec);
@@ -4079,62 +4117,14 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 	}
 	else if (strategy == PARTITION_STRATEGY_LIST)
 	{
-		ListCell   *cell;
-		char	   *colname;
-		Oid			coltype;
-		int32		coltypmod;
-		Oid			partcollation;
-
 		if (spec->strategy != PARTITION_STRATEGY_LIST)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("invalid bound specification for a list partition"),
 					 parser_errposition(pstate, exprLocation((Node *) spec))));
 
-		/* Get the only column's name in case we need to output an error */
-		if (key->partattrs[0] != 0)
-			colname = get_attname(RelationGetRelid(parent),
-								  key->partattrs[0], false);
-		else
-			colname = deparse_expression((Node *) linitial(partexprs),
-										 deparse_context_for(RelationGetRelationName(parent),
-															 RelationGetRelid(parent)),
-										 false, false);
-		/* Need its type data too */
-		coltype = get_partition_col_typid(key, 0);
-		coltypmod = get_partition_col_typmod(key, 0);
-		partcollation = get_partition_col_collation(key, 0);
-
-		result_spec->listdatums = NIL;
-		foreach(cell, spec->listdatums)
-		{
-			Node	   *expr = lfirst(cell);
-			Const	   *value;
-			ListCell   *cell2;
-			bool		duplicate;
-
-			value = transformPartitionBoundValue(pstate, expr,
-												 colname, coltype, coltypmod,
-												 partcollation);
-
-			/* Don't add to the result if the value is a duplicate */
-			duplicate = false;
-			foreach(cell2, result_spec->listdatums)
-			{
-				Const	   *value2 = castNode(Const, lfirst(cell2));
-
-				if (equal(value, value2))
-				{
-					duplicate = true;
-					break;
-				}
-			}
-			if (duplicate)
-				continue;
-
-			result_spec->listdatums = lappend(result_spec->listdatums,
-											  value);
-		}
+		result_spec->listdatums =
+			transformPartitionListBounds(pstate, spec, parent);
 	}
 	else if (strategy == PARTITION_STRATEGY_RANGE)
 	{
@@ -4171,6 +4161,111 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 }
 
 /*
+ * transformPartitionListBounds
+ * 		This functions converts the expressions of list partition bounds
+ * 		from the raw grammar representation.
+ */
+static List *
+transformPartitionListBounds(ParseState *pstate, PartitionBoundSpec *spec,
+							 Relation parent)
+{
+	int				i = 0;
+	int				j = 0;
+	ListCell	   *cell = NULL;
+	List		   *result = NIL;
+	PartitionKey	key = RelationGetPartitionKey(parent);
+	List		   *partexprs = get_partition_exprs(key);
+	int				partnatts = get_partition_natts(key);
+	char		  **colname = (char **) palloc0(partnatts * sizeof(char *));
+	Oid			   *coltype = palloc0(partnatts * sizeof(Oid));
+	int32		   *coltypmod = palloc0(partnatts * sizeof(int));
+	Oid			   *partcollation = palloc0(partnatts * sizeof(Oid));
+
+	for (i = 0; i < partnatts; i++)
+	{
+		if (key->partattrs[i] != 0)
+		{
+			colname[i] = (char *) palloc0(NAMEDATALEN * sizeof(char));
+			colname[i] = get_attname(RelationGetRelid(parent),
+									 key->partattrs[i], false);
+		}
+		else
+		{
+			colname[i] =
+				deparse_expression((Node *) list_nth(partexprs, j),
+								   deparse_context_for(RelationGetRelationName(parent),
+													   RelationGetRelid(parent)),
+								   false, false);
+			++j;
+		}
+
+		coltype[i] = get_partition_col_typid(key, i);
+		coltypmod[i] = get_partition_col_typmod(key, i);
+		partcollation[i] = get_partition_col_collation(key, i);
+	}
+
+	foreach(cell, spec->listdatums)
+	{
+		Node	   *expr = lfirst(cell);
+		List	   *values = NIL;
+		bool		isDuplicate = false;
+
+		if (partnatts == 1)
+		{
+			Const	   *val =
+				transformPartitionBoundValue(pstate, expr,colname[0],
+											 coltype[0], coltypmod[0],
+											 partcollation[0]);
+			values = lappend(values, val);
+		}
+		else
+		{
+			ListCell   *cell2 = NULL;
+			RowExpr		*rowexpr = NULL;
+
+			if (!IsA(expr, RowExpr))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						errmsg("Invalid list bound specification"),
+						parser_errposition(pstate, exprLocation((Node *) spec))));
+
+			rowexpr = (RowExpr *) expr;
+			if (partnatts != list_length(rowexpr->args))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("Must specify exactly one value per partitioning column"),
+						 parser_errposition(pstate, exprLocation((Node *) spec))));
+
+			i = 0;
+			foreach(cell2, rowexpr->args)
+			{
+				Node       *expr = lfirst(cell2);
+				Const      *val =
+					transformPartitionBoundValue(pstate, expr, colname[i],
+												 coltype[i], coltypmod[i],
+												 partcollation[i]);
+				values = lappend(values, val);
+				i++;
+			}
+		}
+
+		/* Don't add to the result if the value is a duplicate */
+		isDuplicate = checkForDuplicates(result, values);
+		if (isDuplicate)
+			continue;
+
+		result = lappend(result, values);
+	}
+
+	pfree(colname);
+	pfree(coltype);
+	pfree(coltypmod);
+	pfree(partcollation);
+
+	return result;
+}
+
+/*
  * transformPartitionRangeBounds
  *		This converts the expressions for range partition bounds from the raw
  *		grammar representation to PartitionRangeDatum structs
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 7925fcc..bb6cddc 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -53,11 +53,12 @@ typedef struct PartitionHashBound
 	int			index;
 } PartitionHashBound;
 
-/* One value coming from some (index'th) list partition */
+/* One bound of a list partition */
 typedef struct PartitionListValue
 {
 	int			index;
-	Datum		value;
+	Datum	   *values;
+	bool	   *isnulls;
 } PartitionListValue;
 
 /* One bound of a range partition */
@@ -175,6 +176,7 @@ static void generate_matching_part_pairs(RelOptInfo *outer_rel,
 										 List **inner_parts);
 static PartitionBoundInfo build_merged_partition_bounds(char strategy,
 														List *merged_datums,
+														List *merged_isnulls,
 														List *merged_kinds,
 														List *merged_indexes,
 														int null_index,
@@ -230,6 +232,7 @@ static Oid	get_partition_operator(PartitionKey key, int col,
 								   StrategyNumber strategy, bool *need_relabel);
 static List *get_qual_for_hash(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_list(Relation parent, PartitionBoundSpec *spec);
+static List *get_qual_for_multi_column_list(Relation parent, PartitionBoundSpec *spec);
 static List *get_qual_for_range(Relation parent, PartitionBoundSpec *spec,
 								bool for_default);
 static void get_range_key_properties(PartitionKey key, int keynum,
@@ -367,8 +370,8 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
 		palloc0(sizeof(PartitionBoundInfoData));
 	boundinfo->strategy = key->strategy;
 	/* No special hash partitions. */
-	boundinfo->null_index = -1;
 	boundinfo->default_index = -1;
+	boundinfo->isnulls = NULL;
 
 	ndatums = nparts;
 	hbounds = (PartitionHashBound **)
@@ -433,6 +436,56 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
 }
 
 /*
+ * partition_bound_accepts_nulls
+ * 		Returns TRUE if partition bound has NULL value, FALSE otherwise.
+ */
+bool partition_bound_accepts_nulls(PartitionBoundInfo boundinfo)
+{
+	int i = 0;
+	int j = 0;
+
+	if (!boundinfo->isnulls)
+		return false;
+
+	for (i = 0; i < boundinfo->ndatums; i++)
+	{
+		//TODO: Handle for multi-column cases
+		for (j = 0; j < 1; j++)
+		{
+			if (boundinfo->isnulls[i][j])
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/*
+ * get_partition_bound_null_index
+ * 		Returns the partition index of the partition bound which accepts NULL.
+ */
+int get_partition_bound_null_index(PartitionBoundInfo boundinfo)
+{
+	int i = 0;
+	int j = 0;
+
+	if (!boundinfo->isnulls)
+		return -1;
+
+	for (i = 0; i < boundinfo->ndatums; i++)
+	{
+		//TODO: Handle for multi-column cases
+		for (j = 0; j < 1; j++)
+		{
+			if (boundinfo->isnulls[i][j])
+				return boundinfo->indexes[i];
+		}
+	}
+
+	return -1;
+}
+
+/*
  * create_list_bounds
  *		Create a PartitionBoundInfo for a list partitioned table
  */
@@ -447,14 +500,12 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	int			ndatums = 0;
 	int			next_index = 0;
 	int			default_index = -1;
-	int			null_index = -1;
 	List	   *non_null_values = NIL;
 
 	boundinfo = (PartitionBoundInfoData *)
 		palloc0(sizeof(PartitionBoundInfoData));
 	boundinfo->strategy = key->strategy;
 	/* Will be set correctly below. */
-	boundinfo->null_index = -1;
 	boundinfo->default_index = -1;
 
 	/* Create a unified list of non-null values across all partitions. */
@@ -479,29 +530,30 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 
 		foreach(c, spec->listdatums)
 		{
-			Const	   *val = castNode(Const, lfirst(c));
+			int					j = 0;
+			List			   *elem = lfirst(c);
+			ListCell		   *cell = NULL;
 			PartitionListValue *list_value = NULL;
 
-			if (!val->constisnull)
-			{
-				list_value = (PartitionListValue *)
-					palloc0(sizeof(PartitionListValue));
-				list_value->index = i;
-				list_value->value = val->constvalue;
-			}
-			else
+			list_value = (PartitionListValue *)
+				palloc0(sizeof(PartitionListValue));
+			list_value->index = i;
+			list_value->values = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+			list_value->isnulls = (bool *) palloc0(key->partnatts * sizeof(bool));
+
+			foreach(cell, elem)
 			{
-				/*
-				 * Never put a null into the values array; save the index of
-				 * the partition that stores nulls, instead.
-				 */
-				if (null_index != -1)
-					elog(ERROR, "found null more than once");
-				null_index = i;
+				Const	   *val = castNode(Const, lfirst(cell));
+
+				if (!val->constisnull)
+					list_value->values[j] = val->constvalue;
+				else
+					list_value->isnulls[j] = true;
+
+				j++;
 			}
 
-			if (list_value)
-				non_null_values = lappend(non_null_values, list_value);
+			non_null_values = lappend(non_null_values, list_value);
 		}
 	}
 
@@ -520,7 +572,8 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 
 		all_values[i] = (PartitionListValue *)
 			palloc(sizeof(PartitionListValue));
-		all_values[i]->value = src->value;
+		all_values[i]->values = src->values;
+		all_values[i]->isnulls= src->isnulls;
 		all_values[i]->index = src->index;
 		i++;
 	}
@@ -530,6 +583,7 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 
 	boundinfo->ndatums = ndatums;
 	boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+	boundinfo->isnulls = (bool **) palloc0(ndatums * sizeof(bool *));
 	boundinfo->nindexes = ndatums;
 	boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
@@ -541,12 +595,19 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	 */
 	for (i = 0; i < ndatums; i++)
 	{
+		int			j = 0;
 		int			orig_index = all_values[i]->index;
+		boundinfo->datums[i] = (Datum *) palloc(key->partnatts * sizeof(Datum));
+		boundinfo->isnulls[i] = (bool *) palloc(key->partnatts * sizeof(bool));
 
-		boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
-		boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
-											key->parttypbyval[0],
-											key->parttyplen[0]);
+		for (j = 0; j < key->partnatts; j++)
+		{
+			if (!all_values[i]->isnulls[j])
+				boundinfo->datums[i][j] = datumCopy(all_values[i]->values[j],
+													key->parttypbyval[j],
+													key->parttyplen[j]);
+			boundinfo->isnulls[i][j] = all_values[i]->isnulls[j];
+		}
 
 		/* If the old index has no mapping, assign one */
 		if ((*mapping)[orig_index] == -1)
@@ -555,22 +616,6 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
 		boundinfo->indexes[i] = (*mapping)[orig_index];
 	}
 
-	/*
-	 * Set the canonical value for null_index, if any.
-	 *
-	 * It is possible that the null-accepting partition has not been assigned
-	 * an index yet, which could happen if such partition accepts only null
-	 * and hence not handled in the above loop which only looked at non-null
-	 * values.
-	 */
-	if (null_index != -1)
-	{
-		Assert(null_index >= 0);
-		if ((*mapping)[null_index] == -1)
-			(*mapping)[null_index] = next_index++;
-		boundinfo->null_index = (*mapping)[null_index];
-	}
-
 	/* Set the canonical value for default_index, if any. */
 	if (default_index != -1)
 	{
@@ -611,10 +656,9 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
 	boundinfo = (PartitionBoundInfoData *)
 		palloc0(sizeof(PartitionBoundInfoData));
 	boundinfo->strategy = key->strategy;
-	/* There is no special null-accepting range partition. */
-	boundinfo->null_index = -1;
 	/* Will be set correctly below. */
 	boundinfo->default_index = -1;
+	boundinfo->isnulls = NULL;
 
 	all_bounds = (PartitionRangeBound **)
 		palloc0(2 * nparts * sizeof(PartitionRangeBound *));
@@ -802,6 +846,8 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 					   PartitionBoundInfo b1, PartitionBoundInfo b2)
 {
 	int			i;
+	bool		b1_isnull = false;
+	bool		b2_isnull = false;
 
 	if (b1->strategy != b2->strategy)
 		return false;
@@ -812,7 +858,7 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 	if (b1->nindexes != b2->nindexes)
 		return false;
 
-	if (b1->null_index != b2->null_index)
+	if (get_partition_bound_null_index(b1) != get_partition_bound_null_index(b2))
 		return false;
 
 	if (b1->default_index != b2->default_index)
@@ -885,7 +931,22 @@ partition_bounds_equal(int partnatts, int16 *parttyplen, bool *parttypbyval,
 				 * context.  datumIsEqual() should be simple enough to be
 				 * safe.
 				 */
-				if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
+				if (b1->isnulls)
+					b1_isnull = b1->isnulls[i][j];
+				if (b2->isnulls)
+					b2_isnull = b2->isnulls[i][j];
+
+				/*
+				 * If any of the partition bound has NULL value, then check
+				 * equality for the NULL value. Dont compare the datums
+				 * as it does not contain valid value in case of NULL.
+				 */
+				if (b1_isnull || b2_isnull)
+				{
+					if (b1_isnull != b2_isnull)
+						return false;
+				}
+				else if (!datumIsEqual(b1->datums[i][j], b2->datums[i][j],
 								  parttypbyval[j], parttyplen[j]))
 					return false;
 			}
@@ -922,10 +983,11 @@ partition_bounds_copy(PartitionBoundInfo src,
 	nindexes = dest->nindexes = src->nindexes;
 	partnatts = key->partnatts;
 
-	/* List partitioned tables have only a single partition key. */
-	Assert(key->strategy != PARTITION_STRATEGY_LIST || partnatts == 1);
-
 	dest->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
+	if (src->isnulls)
+		dest->isnulls = (bool **) palloc(sizeof(bool *) * ndatums);
+	else
+		dest->isnulls = NULL;
 
 	if (src->kind != NULL)
 	{
@@ -955,6 +1017,8 @@ partition_bounds_copy(PartitionBoundInfo src,
 		int			j;
 
 		dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
+		if (src->isnulls)
+			dest->isnulls[i] = (bool *) palloc(sizeof(bool) * natts);
 
 		for (j = 0; j < natts; j++)
 		{
@@ -972,17 +1036,22 @@ partition_bounds_copy(PartitionBoundInfo src,
 				typlen = key->parttyplen[j];
 			}
 
-			if (dest->kind == NULL ||
-				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE)
+
+			if ((dest->kind == NULL ||
+				dest->kind[i][j] == PARTITION_RANGE_DATUM_VALUE) &&
+				(key->strategy != PARTITION_STRATEGY_LIST ||
+				 !src->isnulls[i][j]))
 				dest->datums[i][j] = datumCopy(src->datums[i][j],
 											   byval, typlen);
+
+			if (src->isnulls)
+				dest->isnulls[i][j] = src->isnulls[i][j];
 		}
 	}
 
 	dest->indexes = (int *) palloc(sizeof(int) * nindexes);
 	memcpy(dest->indexes, src->indexes, sizeof(int) * nindexes);
 
-	dest->null_index = src->null_index;
 	dest->default_index = src->default_index;
 
 	return dest;
@@ -1100,6 +1169,8 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 	int			inner_default = inner_bi->default_index;
 	bool		outer_has_null = partition_bound_accepts_nulls(outer_bi);
 	bool		inner_has_null = partition_bound_accepts_nulls(inner_bi);
+	int			outer_null_index = get_partition_bound_null_index(outer_bi);
+	int			inner_null_index = get_partition_bound_null_index(inner_bi);
 	PartitionMap outer_map;
 	PartitionMap inner_map;
 	int			outer_pos;
@@ -1109,6 +1180,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 	int			default_index = -1;
 	List	   *merged_datums = NIL;
 	List	   *merged_indexes = NIL;
+	List	   *merged_isnulls = NIL;
 
 	Assert(*outer_parts == NIL);
 	Assert(*inner_parts == NIL);
@@ -1146,6 +1218,35 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		int			cmpval;
 		Datum	   *merged_datum = NULL;
 		int			merged_index = -1;
+		bool	   *outer_isnull;
+		bool	   *inner_isnull;
+		bool	   *merged_isnull = NULL;
+		bool 		is_all_match = false;
+
+		if (outer_bi->isnulls && outer_pos < outer_bi->ndatums)
+			outer_isnull = outer_bi->isnulls[outer_pos];
+
+		if (inner_bi->isnulls && inner_pos < inner_bi->ndatums)
+			inner_isnull = inner_bi->isnulls[inner_pos];
+
+		//TODO: Handle for multi-column case.
+		if (outer_isnull[0] && inner_isnull[0])
+		{
+			outer_pos++;
+			inner_pos++;
+			continue;
+		}
+		else if (outer_isnull[0])
+		{
+			outer_pos++;
+			continue;
+		}
+		else if (inner_isnull[0])
+		{
+			inner_pos++;
+			continue;
+		}
+
 
 		if (outer_pos < outer_bi->ndatums)
 		{
@@ -1196,13 +1297,15 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		else
 		{
 			Assert(outer_datums != NULL && inner_datums != NULL);
-			cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
-													 partcollation[0],
-													 outer_datums[0],
-													 inner_datums[0]));
+			//TODO: handle multi-column case
+			cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation, 1,  //TODO: get attr count
+												outer_datums,
+												outer_isnull,
+												inner_datums,
+												inner_isnull, &is_all_match);
 		}
 
-		if (cmpval == 0)
+		if (is_all_match)
 		{
 			/* Two list values match exactly. */
 			Assert(outer_pos < outer_bi->ndatums);
@@ -1221,6 +1324,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 				goto cleanup;
 
 			merged_datum = outer_datums;
+			merged_isnull = outer_isnull;
 
 			/* Move to the next pair of list values. */
 			outer_pos++;
@@ -1254,6 +1358,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 				if (merged_index == -1)
 					goto cleanup;
 				merged_datum = outer_datums;
+				merged_isnull = outer_isnull;
 			}
 
 			/* Move to the next list value on the outer side. */
@@ -1288,6 +1393,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 				if (merged_index == -1)
 					goto cleanup;
 				merged_datum = inner_datums;
+				merged_isnull = inner_isnull;
 			}
 
 			/* Move to the next list value on the inner side. */
@@ -1302,6 +1408,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		{
 			merged_datums = lappend(merged_datums, merged_datum);
 			merged_indexes = lappend_int(merged_indexes, merged_index);
+			merged_isnulls = lappend(merged_isnulls, merged_isnull);
 		}
 	}
 
@@ -1310,17 +1417,17 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 	 * non-existent.
 	 */
 	if (outer_has_null &&
-		is_dummy_partition(outer_rel, outer_bi->null_index))
+		is_dummy_partition(outer_rel, outer_null_index))
 		outer_has_null = false;
 	if (inner_has_null &&
-		is_dummy_partition(inner_rel, inner_bi->null_index))
+		is_dummy_partition(inner_rel, inner_null_index))
 		inner_has_null = false;
 
 	/* Merge the NULL partitions if any. */
 	if (outer_has_null || inner_has_null)
 		merge_null_partitions(&outer_map, &inner_map,
 							  outer_has_null, inner_has_null,
-							  outer_bi->null_index, inner_bi->null_index,
+							  outer_null_index, inner_null_index,
 							  jointype, &next_index, &null_index);
 	else
 		Assert(null_index == -1);
@@ -1358,6 +1465,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 		/* Make a PartitionBoundInfo struct to return. */
 		merged_bounds = build_merged_partition_bounds(outer_bi->strategy,
 													  merged_datums,
+													  merged_isnulls,
 													  NIL,
 													  merged_indexes,
 													  null_index,
@@ -1368,6 +1476,7 @@ merge_list_bounds(FmgrInfo *partsupfunc, Oid *partcollation,
 cleanup:
 	/* Free local memory before returning. */
 	list_free(merged_datums);
+	list_free(merged_isnulls);
 	list_free(merged_indexes);
 	free_partition_map(&outer_map);
 	free_partition_map(&inner_map);
@@ -1676,6 +1785,7 @@ merge_range_bounds(int partnatts, FmgrInfo *partsupfuncs,
 		/* Make a PartitionBoundInfo struct to return. */
 		merged_bounds = build_merged_partition_bounds(outer_bi->strategy,
 													  merged_datums,
+													  NIL,
 													  merged_kinds,
 													  merged_indexes,
 													  -1,
@@ -2407,19 +2517,41 @@ generate_matching_part_pairs(RelOptInfo *outer_rel, RelOptInfo *inner_rel,
  */
 static PartitionBoundInfo
 build_merged_partition_bounds(char strategy, List *merged_datums,
-							  List *merged_kinds, List *merged_indexes,
-							  int null_index, int default_index)
+							  List *merged_isnulls, List *merged_kinds,
+							  List *merged_indexes, int null_index,
+							  int default_index)
 {
 	PartitionBoundInfo merged_bounds;
 	int			ndatums = list_length(merged_datums);
 	int			pos;
 	ListCell   *lc;
+	int			natts = 1;  //TODO: Handle for multi-column case
+	bool	   *null = NULL;
 
 	merged_bounds = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
 	merged_bounds->strategy = strategy;
-	merged_bounds->ndatums = ndatums;
 
+	if (merged_isnulls)
+	{
+		if (null_index >= 0)
+		{
+			null = (bool *) palloc0(sizeof(bool) * natts);
+			null[0] = true;
+			ndatums++;
+		}
+		merged_bounds->isnulls = (bool **) palloc(sizeof(bool *) * ndatums);
+
+		pos = 0;
+		foreach(lc, merged_isnulls)
+			merged_bounds->isnulls[pos++] = (bool *) lfirst(lc);
+
+		if (null_index >= 0)
+			merged_bounds->isnulls[pos] = null;
+	}
+
+	merged_bounds->ndatums = ndatums;
 	merged_bounds->datums = (Datum **) palloc(sizeof(Datum *) * ndatums);
+
 	pos = 0;
 	foreach(lc, merged_datums)
 		merged_bounds->datums[pos++] = (Datum *) lfirst(lc);
@@ -2436,6 +2568,7 @@ build_merged_partition_bounds(char strategy, List *merged_datums,
 		/* There are ndatums+1 indexes in the case of range partitioning. */
 		merged_indexes = lappend_int(merged_indexes, -1);
 		ndatums++;
+		merged_bounds->isnulls = NULL;
 	}
 	else
 	{
@@ -2444,14 +2577,17 @@ build_merged_partition_bounds(char strategy, List *merged_datums,
 		merged_bounds->kind = NULL;
 	}
 
-	Assert(list_length(merged_indexes) == ndatums);
+	Assert(list_length(merged_indexes) == ndatums ||
+		   list_length(merged_indexes) == ndatums - 1);
 	merged_bounds->nindexes = ndatums;
 	merged_bounds->indexes = (int *) palloc(sizeof(int) * ndatums);
 	pos = 0;
 	foreach(lc, merged_indexes)
 		merged_bounds->indexes[pos++] = lfirst_int(lc);
 
-	merged_bounds->null_index = null_index;
+	if (merged_isnulls && null_index >= 0)
+		merged_bounds->indexes[pos] = null_index;
+
 	merged_bounds->default_index = default_index;
 
 	return merged_bounds;
@@ -2953,32 +3089,37 @@ check_new_partition_bound(char *relname, Relation parent,
 
 					foreach(cell, spec->listdatums)
 					{
-						Const	   *val = castNode(Const, lfirst(cell));
-
-						overlap_location = val->location;
-						if (!val->constisnull)
+						int			i = 0;
+						int         offset = -1;
+						bool        equal = false;
+						List	   *elem = lfirst(cell);
+						Datum	   *values = (Datum *) palloc0(key->partnatts * sizeof(Datum));
+						bool	   *isnulls = (bool *) palloc0(key->partnatts * sizeof(bool));
+
+						for (i = 0; i < key->partnatts; i++)
 						{
-							int			offset;
-							bool		equal;
-
-							offset = partition_list_bsearch(&key->partsupfunc[0],
-															key->partcollation,
-															boundinfo,
-															val->constvalue,
-															&equal);
-							if (offset >= 0 && equal)
-							{
-								overlap = true;
-								with = boundinfo->indexes[offset];
-								break;
-							}
+							Const	   *val = castNode(Const, list_nth(elem, i));
+
+							values[i] = val->constvalue;
+							isnulls[i] = val->constisnull;
+							overlap_location = val->location;
 						}
-						else if (partition_bound_accepts_nulls(boundinfo))
+
+						offset = partition_list_bsearch(key->partsupfunc,
+														key->partcollation,
+														boundinfo,
+														key->partnatts,
+														values, isnulls,
+														&equal);
+						if (offset >= 0 && equal)
 						{
 							overlap = true;
-							with = boundinfo->null_index;
+							with = boundinfo->indexes[offset];
 							break;
 						}
+
+						pfree(values);
+						pfree(isnulls);
 					}
 				}
 
@@ -3491,6 +3632,53 @@ partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
 }
 
 /*
+ * partition_lbound_datum_cmp
+ *
+ * This function compares the list bound values of all the partition key
+ * columns. Returns the value of 'cmpval' if the first bound value does
+ * not match, otherwise returns zero. If it successfully compares the bound
+ * values for all the partition key, then it sets is_all_match to TRUE.
+ */
+int32
+partition_lbound_datum_cmp(FmgrInfo *partsupfunc, Oid *partcollation,
+						   int nvalues, Datum *lb_datums, bool *lb_isnulls,
+						   Datum *values, bool *isnulls, bool *is_all_match)
+{
+	int		i = 0;
+	int32	cmpval = 0;
+
+	for (i = 0; i < nvalues; i++)
+	{
+		bool isnull = false;
+
+		if (isnulls)
+			isnull = isnulls[i];
+
+		if (lb_isnulls[i] && isnull)
+			cmpval = 0;
+		else if (lb_isnulls[i])
+			cmpval = 1;
+		else if (isnull)
+			cmpval = -1;
+		else
+			cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[i],
+													 partcollation[i],
+													 lb_datums[i], values[i]));
+
+		if (cmpval != 0)
+			break;
+	}
+
+	if (i == 0)
+		return cmpval;
+
+	if (i == nvalues && cmpval == 0)
+		*is_all_match = true;
+
+	return 0;
+}
+
+/*
  * partition_list_bsearch
  *		Returns the index of the greatest bound datum that is less than equal
  * 		to the given value or -1 if all of the bound datums are greater
@@ -3500,8 +3688,8 @@ partition_hbound_cmp(int modulus1, int remainder1, int modulus2, int remainder2)
  */
 int
 partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
-					   PartitionBoundInfo boundinfo,
-					   Datum value, bool *is_equal)
+					   PartitionBoundInfo boundinfo, int nvalues,
+					   Datum *values, bool *isnulls, bool *is_equal)
 {
 	int			lo,
 				hi,
@@ -3512,16 +3700,76 @@ partition_list_bsearch(FmgrInfo *partsupfunc, Oid *partcollation,
 	while (lo < hi)
 	{
 		int32		cmpval;
+		bool		is_all_match = false;
 
 		mid = (lo + hi + 1) / 2;
-		cmpval = DatumGetInt32(FunctionCall2Coll(&partsupfunc[0],
-												 partcollation[0],
-												 boundinfo->datums[mid][0],
-												 value));
+		cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation,
+											nvalues, boundinfo->datums[mid],
+											boundinfo->isnulls[mid], values,
+											isnulls, &is_all_match);
+
+		if (is_all_match)
+		{
+			*is_equal = true;
+			return mid;
+		}
+
+		if (cmpval == 0)
+		{
+			/*
+			 * Once we find the matching for the first column but if it does not
+			 * match for the any of the other columns, then the binary search
+			 * will not work in all the cases. We should traverse just below
+			 * and above the mid index until we find the match or we reach the
+			 * first mismatch.
+			 */
+			int32	off = mid;
+			while (off >= 1)
+			{
+				cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation,
+													nvalues, boundinfo->datums[off - 1],
+													boundinfo->isnulls[off - 1], values,
+													isnulls, &is_all_match);
+
+				if (is_all_match)
+				{
+					/* Found the matching bound. Return the offset. */
+					*is_equal = true;
+					return (off - 1);
+				}
+
+				if (cmpval != 0)
+					break;
+
+				off--;
+			}
+
+			off = mid;
+			while (off < boundinfo->ndatums - 1)
+			{
+				cmpval = partition_lbound_datum_cmp(partsupfunc, partcollation,
+													nvalues, boundinfo->datums[off + 1],
+													boundinfo->isnulls[off + 1], values,
+													isnulls, &is_all_match);
+
+				if (is_all_match)
+				{
+					/* Found the matching bound. Return the offset. */
+					*is_equal = true;
+					return (off + 1);
+				}
+
+				if (cmpval != 0)
+					break;
+
+				off++;
+			}
+		}
+
 		if (cmpval <= 0)
 		{
 			lo = mid;
-			*is_equal = (cmpval == 0);
+			*is_equal = (cmpval == 0 && is_all_match);
 			if (*is_equal)
 				break;
 		}
@@ -3687,13 +3935,23 @@ qsort_partition_hbound_cmp(const void *a, const void *b)
 static int32
 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
 {
-	Datum		val1 = (*(PartitionListValue *const *) a)->value,
-				val2 = (*(PartitionListValue *const *) b)->value;
+	Datum	   *val1 = (*(PartitionListValue *const *) a)->values;
+	Datum	   *val2 = (*(PartitionListValue *const *) b)->values;
+	bool	   *null1 = (*(PartitionListValue *const *) a)->isnulls;
+	bool	   *null2 = (*(PartitionListValue *const *) b)->isnulls;
+
 	PartitionKey key = (PartitionKey) arg;
 
-	return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
-										   key->partcollation[0],
-										   val1, val2));
+	if (null1[0] && null2[0])
+		return 0;
+	else if (null1[0])
+		return 1;
+	else if (null2[0])
+		return -1;
+	else
+		return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],
+											   key->partcollation[0],
+											   val1[0], val2[0]));
 }
 
 /*
@@ -3793,9 +4051,8 @@ make_partition_op_expr(PartitionKey key, int keynum,
 				int			nelems = list_length(elems);
 
 				Assert(nelems >= 1);
-				Assert(keynum == 0);
 
-				if (nelems > 1 &&
+				if (key->partnatts == 1 && nelems > 1 &&
 					!type_is_array(key->parttypid[keynum]))
 				{
 					ArrayExpr  *arrexpr;
@@ -3820,10 +4077,9 @@ make_partition_op_expr(PartitionKey key, int keynum,
 					saopexpr->inputcollid = key->partcollation[keynum];
 					saopexpr->args = list_make2(arg1, arrexpr);
 					saopexpr->location = -1;
-
 					result = (Expr *) saopexpr;
 				}
-				else
+				else if (key->partnatts == 1)
 				{
 					List	   *elemops = NIL;
 					ListCell   *lc;
@@ -3844,9 +4100,17 @@ make_partition_op_expr(PartitionKey key, int keynum,
 
 					result = nelems > 1 ? makeBoolExpr(OR_EXPR, elemops, -1) : linitial(elemops);
 				}
+				else
+				{
+					result = make_opclause(operoid,
+										   BOOLOID,
+										   false,
+										   arg1, arg2,
+										   InvalidOid,
+										   key->partcollation[keynum]);
+				}
 				break;
 			}
-
 		case PARTITION_STRATEGY_RANGE:
 			result = make_opclause(operoid,
 								   BOOLOID,
@@ -3968,11 +4232,8 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 	List	   *elems = NIL;
 	bool		list_has_null = false;
 
-	/*
-	 * Only single-column list partitioning is supported, so we are worried
-	 * only about the partition key with index 0.
-	 */
-	Assert(key->partnatts == 1);
+	if (key->partnatts > 1)
+		return get_qual_for_multi_column_list(parent, spec);
 
 	/* Construct Var or expression representing the partition column */
 	if (key->partattrs[0] != 0)
@@ -3998,13 +4259,8 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 		PartitionBoundInfo boundinfo = pdesc->boundinfo;
 
 		if (boundinfo)
-		{
 			ndatums = boundinfo->ndatums;
 
-			if (partition_bound_accepts_nulls(boundinfo))
-				list_has_null = true;
-		}
-
 		/*
 		 * If default is the only partition, there need not be any partition
 		 * constraint on it.
@@ -4016,6 +4272,12 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 		{
 			Const	   *val;
 
+			if (boundinfo->isnulls[i][0])
+			{
+				list_has_null = true;
+				continue;
+			}
+
 			/*
 			 * Construct Const from known-not-null datum.  We must be careful
 			 * to copy the value, because our result has to be able to outlive
@@ -4025,7 +4287,7 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 							key->parttypmod[0],
 							key->parttypcoll[0],
 							key->parttyplen[0],
-							datumCopy(*boundinfo->datums[i],
+							datumCopy(boundinfo->datums[i][0],
 									  key->parttypbyval[0],
 									  key->parttyplen[0]),
 							false,	/* isnull */
@@ -4041,12 +4303,17 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 		 */
 		foreach(cell, spec->listdatums)
 		{
-			Const	   *val = castNode(Const, lfirst(cell));
+			ListCell	   *cell2 = NULL;
 
-			if (val->constisnull)
-				list_has_null = true;
-			else
-				elems = lappend(elems, copyObject(val));
+			foreach(cell2, (List *) lfirst(cell))
+			{
+				Const      *val = castNode(Const, lfirst(cell2));
+
+				if (val->constisnull)
+					list_has_null = true;
+				else
+					elems = lappend(elems, copyObject(val));
+			}
 		}
 	}
 
@@ -4122,6 +4389,158 @@ get_qual_for_list(Relation parent, PartitionBoundSpec *spec)
 }
 
 /*
+ * get_qual_for_list_for_multi_column
+ *
+ * Returns a list of expressions to use as a list partition's constraint,
+ * given the parent relation and partition bound structure.
+ *
+ * The function returns NIL for a default partition when it's the only
+ * partition since in that case there is no constraint.
+ */
+static List *
+get_qual_for_multi_column_list(Relation parent, PartitionBoundSpec *spec)
+{
+	int			i = 0;
+	int			j = 0;
+	PartitionKey key = RelationGetPartitionKey(parent);
+	List	   *result;
+	Expr	   *opexpr;
+	NullTest   *nulltest;
+	ListCell   *cell;
+	List	   *elems = NIL;
+	Expr      **keyCol = (Expr **) palloc0 (key->partnatts * sizeof(Expr *));
+
+	/* Construct Var or expression representing the partition columns */
+	for (i = 0; i < key->partnatts; i++)
+	{
+		if (key->partattrs[i] != 0)
+			keyCol[i] = (Expr *) makeVar(1,
+									  key->partattrs[i],
+									  key->parttypid[i],
+									  key->parttypmod[i],
+									  key->parttypcoll[i],
+									  0);
+		else
+		{
+			keyCol[i] = (Expr *) copyObject(list_nth(key->partexprs, j));
+			++j;
+		}
+	}
+
+	/*
+	 * For default list partition, collect datums for all the partitions. The
+	 * default partition constraint should check that the partition key is
+	 * equal to none of those.
+	 */
+	if (spec->is_default)
+	{
+		int			ndatums = 0;
+		PartitionDesc pdesc = RelationGetPartitionDesc(parent, false);
+		PartitionBoundInfo boundinfo = pdesc->boundinfo;
+
+		if (boundinfo)
+			ndatums = boundinfo->ndatums;
+
+		/*
+		 * If default is the only partition, there need not be any partition
+		 * constraint on it.
+		 */
+		if (ndatums == 0)
+			return NIL;
+
+		for (i = 0; i < ndatums; i++)
+		{
+			List       *andexpr = NIL;
+
+			for (j = 0; j < key->partnatts; j++)
+			{
+				Const      *val = NULL;
+
+				if (boundinfo->isnulls[i][j])
+				{
+					nulltest = makeNode(NullTest);
+					nulltest->arg = keyCol[j];
+					nulltest->nulltesttype = IS_NULL;
+					nulltest->argisrow = false;
+					nulltest->location = -1;
+					andexpr = lappend(andexpr, nulltest);
+				}
+				else
+				{
+					val = makeConst(key->parttypid[j],
+									key->parttypmod[j],
+									key->parttypcoll[j],
+									key->parttyplen[j],
+									datumCopy(boundinfo->datums[i][j],
+											  key->parttypbyval[j],
+											  key->parttyplen[j]),
+									false,  /* isnull */
+									key->parttypbyval[j]);
+
+					opexpr = make_partition_op_expr(key, j, BTEqualStrategyNumber,
+													keyCol[j], (Expr *) val);
+					andexpr = lappend(andexpr, opexpr);
+				}
+			}
+
+			opexpr = makeBoolExpr(AND_EXPR, andexpr, -1);
+			elems = lappend(elems, opexpr);
+		}
+	}
+	else
+	{
+		/*
+		 * Create list of Consts for the allowed values.
+		 */
+		foreach(cell, spec->listdatums)
+		{
+			List	   *andexpr = NIL;
+			ListCell   *cell2 = NULL;
+
+			j = 0;
+			foreach(cell2, (List *) lfirst(cell))
+			{
+				Const      *val = castNode(Const, lfirst(cell2));
+
+				if (val->constisnull)
+				{
+					nulltest = makeNode(NullTest);
+					nulltest->arg = keyCol[j];
+					nulltest->nulltesttype = IS_NULL;
+					nulltest->argisrow = false;
+					nulltest->location = -1;
+					andexpr = lappend(andexpr, nulltest);
+				}
+				else
+				{
+					opexpr = make_partition_op_expr(key, j, BTEqualStrategyNumber,
+													keyCol[j], (Expr *) val);
+					andexpr = lappend(andexpr, opexpr);
+				}
+				j++;
+			}
+
+			opexpr = makeBoolExpr(AND_EXPR, andexpr, -1);
+			elems = lappend(elems, opexpr);
+		}
+	}
+
+	opexpr = makeBoolExpr(OR_EXPR, elems, -1);
+	result = list_make1(opexpr);
+
+	/*
+	 * Note that, in general, applying NOT to a constraint expression doesn't
+	 * necessarily invert the set of rows it accepts, because NOT (NULL) is
+	 * NULL.  However, the partition constraints we construct here never
+	 * evaluate to NULL, so applying NOT works as intended.
+	 */
+	if (spec->is_default)
+		result = list_make1(makeBoolExpr(NOT_EXPR, result, -1));
+
+	return result;
+}
+
+/*
  * get_qual_for_range
  *
  * Returns an implicit-AND list of expressions to use as a range partition's
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index c793742..d4e9ba3 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -185,7 +185,8 @@ static PruneStepResult *get_matching_hash_bounds(PartitionPruneContext *context,
 												 StrategyNumber opstrategy, Datum *values, int nvalues,
 												 FmgrInfo *partsupfunc, Bitmapset *nullkeys);
 static PruneStepResult *get_matching_list_bounds(PartitionPruneContext *context,
-												 StrategyNumber opstrategy, Datum value, int nvalues,
+												 StrategyNumber opstrategy,
+												 Datum *values, int nvalues,
 												 FmgrInfo *partsupfunc, Bitmapset *nullkeys);
 static PruneStepResult *get_matching_range_bounds(PartitionPruneContext *context,
 												  StrategyNumber opstrategy, Datum *values, int nvalues,
@@ -909,7 +910,8 @@ get_matching_partitions(PartitionPruneContext *context, List *pruning_steps)
 	{
 		Assert(context->strategy == PARTITION_STRATEGY_LIST);
 		Assert(partition_bound_accepts_nulls(context->boundinfo));
-		result = bms_add_member(result, context->boundinfo->null_index);
+		result = bms_add_member(result,
+								get_partition_bound_null_index(context->boundinfo));
 	}
 	if (scan_default)
 	{
@@ -2659,7 +2661,7 @@ get_matching_hash_bounds(PartitionPruneContext *context,
  */
 static PruneStepResult *
 get_matching_list_bounds(PartitionPruneContext *context,
-						 StrategyNumber opstrategy, Datum value, int nvalues,
+						 StrategyNumber opstrategy, Datum *values, int nvalues,
 						 FmgrInfo *partsupfunc, Bitmapset *nullkeys)
 {
 	PruneStepResult *result = (PruneStepResult *) palloc0(sizeof(PruneStepResult));
@@ -2672,7 +2674,6 @@ get_matching_list_bounds(PartitionPruneContext *context,
 	Oid		   *partcollation = context->partcollation;
 
 	Assert(context->strategy == PARTITION_STRATEGY_LIST);
-	Assert(context->partnatts == 1);
 
 	result->scan_null = result->scan_default = false;
 
@@ -2710,9 +2711,19 @@ get_matching_list_bounds(PartitionPruneContext *context,
 	 */
 	if (nvalues == 0)
 	{
+		int i = 0;
+		int j = 0;
+
 		Assert(boundinfo->ndatums > 0);
-		result->bound_offsets = bms_add_range(NULL, 0,
-											  boundinfo->ndatums - 1);
+
+		for (i = 0; i < boundinfo->ndatums; i++)
+		{
+			for (j = 0; j < context->partnatts; j++)
+			{
+				if (!boundinfo->isnulls[i][j])
+					result->bound_offsets = bms_add_member(result->bound_offsets, i);
+			}
+		}
 		result->scan_default = partition_bound_has_default(boundinfo);
 		return result;
 	}
@@ -2720,15 +2731,24 @@ get_matching_list_bounds(PartitionPruneContext *context,
 	/* Special case handling of values coming from a <> operator clause. */
 	if (opstrategy == InvalidStrategy)
 	{
+		int i = 0;
+		int j = 0;
+
 		/*
 		 * First match to all bounds.  We'll remove any matching datums below.
 		 */
 		Assert(boundinfo->ndatums > 0);
-		result->bound_offsets = bms_add_range(NULL, 0,
-											  boundinfo->ndatums - 1);
+		for (i = 0; i < boundinfo->ndatums; i++)
+		{
+			for (j = 0; j < context->partnatts; j++)
+			{
+				if (!boundinfo->isnulls[i][j])
+					result->bound_offsets = bms_add_member(result->bound_offsets, i);
+			}
+		}
 
 		off = partition_list_bsearch(partsupfunc, partcollation, boundinfo,
-									 value, &is_equal);
+									 nvalues, values, NULL, &is_equal);
 		if (off >= 0 && is_equal)
 		{
 
@@ -2760,8 +2780,8 @@ get_matching_list_bounds(PartitionPruneContext *context,
 		case BTEqualStrategyNumber:
 			off = partition_list_bsearch(partsupfunc,
 										 partcollation,
-										 boundinfo, value,
-										 &is_equal);
+										 boundinfo, nvalues,
+										 values, NULL, &is_equal);
 			if (off >= 0 && is_equal)
 			{
 				Assert(boundinfo->indexes[off] >= 0);
@@ -2777,8 +2797,9 @@ get_matching_list_bounds(PartitionPruneContext *context,
 		case BTGreaterStrategyNumber:
 			off = partition_list_bsearch(partsupfunc,
 										 partcollation,
-										 boundinfo, value,
-										 &is_equal);
+										 boundinfo, nvalues,
+										 values, NULL, &is_equal);
+
 			if (off >= 0)
 			{
 				/* We don't want the matched datum to be in the result. */
@@ -2812,8 +2833,9 @@ get_matching_list_bounds(PartitionPruneContext *context,
 		case BTLessStrategyNumber:
 			off = partition_list_bsearch(partsupfunc,
 										 partcollation,
-										 boundinfo, value,
-										 &is_equal);
+										 boundinfo, nvalues,
+										 values, NULL, &is_equal);
+
 			if (off >= 0 && is_equal && !inclusive)
 				off--;
 
@@ -3452,7 +3474,7 @@ perform_pruning_base_step(PartitionPruneContext *context,
 		case PARTITION_STRATEGY_LIST:
 			return get_matching_list_bounds(context,
 											opstep->opstrategy,
-											values[0], nvalues,
+											values, nvalues,
 											&partsupfunc[0],
 											opstep->nullkeys);
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84ad62c..053aa6b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9399,13 +9399,11 @@ get_rule_expr(Node *node, deparse_context *context,
 						sep = "";
 						foreach(cell, spec->listdatums)
 						{
-							Const	   *val = castNode(Const, lfirst(cell));
-
 							appendStringInfoString(buf, sep);
-							get_const_expr(val, context, -1);
+							appendStringInfoString
+								(buf, get_list_partbound_value_string(lfirst(cell)));
 							sep = ", ";
 						}
-
 						appendStringInfoChar(buf, ')');
 						break;
 
@@ -11963,6 +11961,45 @@ flatten_reloptions(Oid relid)
 }
 
 /*
+ * get_list_partbound_value_string
+ * 		A C string representation of one list partition bound value
+ */
+char *
+get_list_partbound_value_string(List *bound_value)
+{
+	StringInfo  	buf = makeStringInfo();
+	StringInfo  	boundconstraint = makeStringInfo();
+	deparse_context context;
+	ListCell	   *cell = NULL;
+	char		   *sep = "";
+	int				ncols = 0;
+
+	memset(&context, 0, sizeof(deparse_context));
+	context.buf = buf;
+
+	foreach(cell, bound_value)
+	{
+		Const      *val = castNode(Const, lfirst(cell));
+
+		appendStringInfoString(buf, sep);
+		get_const_expr(val, &context, -1);
+		sep = ", ";
+		ncols++;
+	}
+
+	if (ncols > 1)
+	{
+		appendStringInfoChar(boundconstraint, '(');
+		appendStringInfoString(boundconstraint, buf->data);
+		appendStringInfoChar(boundconstraint, ')');
+
+		return boundconstraint->data;
+	}
+	else
+		return buf->data;
+}
+
+/*
  * get_range_partbound_string
  *		A C string representation of one range partition bound
  */
diff --git a/src/include/partitioning/partbounds.h b/src/include/partitioning/partbounds.h
index ebf3ff1..a4b301b 100644
--- a/src/include/partitioning/partbounds.h
+++ b/src/include/partitioning/partbounds.h
@@ -67,20 +67,21 @@ typedef struct PartitionBoundInfoData
 	char		strategy;		/* hash, list or range? */
 	int			ndatums;		/* Length of the datums[] array */
 	Datum	  **datums;
+	bool	  **isnulls;
 	PartitionRangeDatumKind **kind; /* The kind of each range bound datum;
 									 * NULL for hash and list partitioned
 									 * tables */
 	int			nindexes;		/* Length of the indexes[] array */
 	int		   *indexes;		/* Partition indexes */
-	int			null_index;		/* Index of the null-accepting partition; -1
-								 * if there isn't one */
 	int			default_index;	/* Index of the default partition; -1 if there
 								 * isn't one */
 } PartitionBoundInfoData;
 
-#define partition_bound_accepts_nulls(bi) ((bi)->null_index != -1)
 #define partition_bound_has_default(bi) ((bi)->default_index != -1)
 
+extern bool partition_bound_accepts_nulls(PartitionBoundInfo boundinfo);
+extern int get_partition_bound_null_index(PartitionBoundInfo boundinfo);
+
 extern int	get_hash_partition_greatest_modulus(PartitionBoundInfo b);
 extern uint64 compute_partition_hash_value(int partnatts, FmgrInfo *partsupfunc,
 										   Oid *partcollation,
@@ -117,12 +118,17 @@ extern int32 partition_rbound_datum_cmp(FmgrInfo *partsupfunc,
 extern int	partition_list_bsearch(FmgrInfo *partsupfunc,
 								   Oid *partcollation,
 								   PartitionBoundInfo boundinfo,
-								   Datum value, bool *is_equal);
+								   int nvalues, Datum *values,
+								   bool *isnulls, bool *is_equal);
 extern int	partition_range_datum_bsearch(FmgrInfo *partsupfunc,
 										  Oid *partcollation,
 										  PartitionBoundInfo boundinfo,
 										  int nvalues, Datum *values, bool *is_equal);
 extern int	partition_hash_bsearch(PartitionBoundInfo boundinfo,
 								   int modulus, int remainder);
-
+extern int32 partition_lbound_datum_cmp(FmgrInfo *partsupfunc,
+										Oid *partcollation,
+										int nvalues, Datum *lb_datums,
+										bool *lb_isnulls, Datum *values,
+										bool *isnulls, bool *is_all_match);
 #endif							/* PARTBOUNDS_H */
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index d333e5e..60dac6d 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -40,6 +40,7 @@ extern List *select_rtable_names_for_explain(List *rtable,
 extern char *generate_collation_name(Oid collid);
 extern char *generate_opclass_name(Oid opclass);
 extern char *get_range_partbound_string(List *bound_datums);
+extern char *get_list_partbound_value_string(List *bound_value);
 
 extern char *pg_get_statisticsobjdef_string(Oid statextid);
 
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index ad89dd0..43f669d 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -352,12 +352,6 @@ CREATE TABLE partitioned (
 	a int
 ) INHERITS (some_table) PARTITION BY LIST (a);
 ERROR:  cannot create partitioned table as inheritance child
--- cannot use more than 1 column as partition key for list partitioned table
-CREATE TABLE partitioned (
-	a1 int,
-	a2 int
-) PARTITION BY LIST (a1, a2);	-- fail
-ERROR:  cannot use "list" partition strategy with more than one column
 -- unsupported constraint type for partitioned tables
 CREATE TABLE partitioned (
 	a int,
@@ -913,6 +907,34 @@ CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue)
 ERROR:  partition "fail_part" would overlap partition "part10"
 LINE 1: ..._part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalu...
                                                              ^
+-- now check for multi-column list partition key
+CREATE TABLE list_parted3 (
+	a int,
+	b varchar
+) PARTITION BY LIST (a, b);
+CREATE TABLE list_parted3_p1 PARTITION OF list_parted3 FOR VALUES IN ((1, 'A'));
+CREATE TABLE list_parted3_p2 PARTITION OF list_parted3 FOR VALUES IN ((1, 'B'),(1, 'E'), (1, 'E'), (2, 'C'),(2, 'D'));
+CREATE TABLE list_parted3_p3 PARTITION OF list_parted3 FOR VALUES IN ((1, NULL),(NULL, 'F'));
+CREATE TABLE list_parted3_p4 PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL));
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, 'E'));
+ERROR:  partition "fail_part" would overlap partition "list_parted3_p2"
+LINE 1: ...ail_part PARTITION OF list_parted3 FOR VALUES IN ((1, 'E'));
+                                                                 ^
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, NULL));
+ERROR:  partition "fail_part" would overlap partition "list_parted3_p3"
+LINE 1: ...il_part PARTITION OF list_parted3 FOR VALUES IN ((1, NULL));
+                                                                ^
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, 'F'));
+ERROR:  partition "fail_part" would overlap partition "list_parted3_p3"
+LINE 1: ..._part PARTITION OF list_parted3 FOR VALUES IN ((NULL, 'F'));
+                                                                 ^
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL));
+ERROR:  partition "fail_part" would overlap partition "list_parted3_p4"
+LINE 1: ...part PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL));
+                                                                ^
+CREATE TABLE list_parted3_default PARTITION OF list_parted3 DEFAULT;
+-- cleanup
+DROP TABLE list_parted3;
 -- check for partition bound overlap and other invalid specifications for the hash partition
 CREATE TABLE hash_parted2 (
 	a varchar
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 5063a3d..174f8c2 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -808,6 +808,63 @@ select tableoid::regclass::text, * from mcrparted order by 1;
 
 -- cleanup
 drop table mcrparted;
+-- Test multi-column list partitioning with 3 partition keys
+create table mclparted (a int, b text, c int) partition by list (a, b, c);
+create table mclparted_p1 partition of mclparted for values in ((1, 'a', 1));
+create table mclparted_p2 partition of mclparted for values in ((1, 'a', 2), (1, 'b', 1), (2, 'a', 1));
+create table mclparted_p3 partition of mclparted for values in ((3, 'c', 3), (4, 'd', 4), (5, 'e', 5), (6, null, 6));
+create table mclparted_p4 partition of mclparted for values in ((null, 'a', 1), (1, null, 1), (1, 'a', null));
+create table mclparted_p5 partition of mclparted for values in ((null, null, null));
+-- routed to mclparted_p1
+insert into mclparted values (1, 'a', 1);
+-- routed to mclparted_p2
+insert into mclparted values (1, 'a', 2);
+insert into mclparted values (1, 'b', 1);
+insert into mclparted values (2, 'a', 1);
+-- routed to mclparted_p3
+insert into mclparted values (3, 'c', 3);
+insert into mclparted values (4, 'd', 4);
+insert into mclparted values (5, 'e', 5);
+insert into mclparted values (6, null, 6);
+-- routed to mclparted_p4
+insert into mclparted values (null, 'a', 1);
+insert into mclparted values (1, null, 1);
+insert into mclparted values (1, 'a', null);
+-- routed to mclparted_p5
+insert into mclparted values (null, null, null);
+-- error cases
+insert into mclparted values (10, 'a', 1);
+ERROR:  no partition of relation "mclparted" found for row
+DETAIL:  Partition key of the failing row contains (a, b, c) = (10, a, 1).
+insert into mclparted values (1, 'z', 1);
+ERROR:  no partition of relation "mclparted" found for row
+DETAIL:  Partition key of the failing row contains (a, b, c) = (1, z, 1).
+insert into mclparted values (1, 'a', 10);
+ERROR:  no partition of relation "mclparted" found for row
+DETAIL:  Partition key of the failing row contains (a, b, c) = (1, a, 10).
+insert into mclparted values (1, null, null);
+ERROR:  no partition of relation "mclparted" found for row
+DETAIL:  Partition key of the failing row contains (a, b, c) = (1, null, null).
+-- check rows
+select tableoid::regclass::text, * from mclparted order by 1;
+   tableoid   | a | b | c 
+--------------+---+---+---
+ mclparted_p1 | 1 | a | 1
+ mclparted_p2 | 1 | b | 1
+ mclparted_p2 | 2 | a | 1
+ mclparted_p2 | 1 | a | 2
+ mclparted_p3 | 3 | c | 3
+ mclparted_p3 | 5 | e | 5
+ mclparted_p3 | 6 |   | 6
+ mclparted_p3 | 4 | d | 4
+ mclparted_p4 |   | a | 1
+ mclparted_p4 | 1 |   | 1
+ mclparted_p4 | 1 | a |  
+ mclparted_p5 |   |   |  
+(12 rows)
+
+-- cleanup
+drop table mclparted;
 -- check that a BR constraint can't make partition contain violating rows
 create table brtrigpartcon (a int, b text) partition by list (a);
 create table brtrigpartcon1 partition of brtrigpartcon for values in (1);
@@ -981,6 +1038,96 @@ select tableoid::regclass, * from mcrparted order by a, b;
 (11 rows)
 
 drop table mcrparted;
+-- check multi-column list partitioning with partition key constraint
+create table mclparted (a text, b int) partition by list(a, b);
+create table mclparted_p1 partition of mclparted for values in (('a', 1));
+create table mclparted_p2 partition of mclparted for values in (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3));
+create table mclparted_p3 partition of mclparted for values in (('a', 3), ('a', 4), ('a', null), (null, 1));
+create table mclparted_p4 partition of mclparted for values in (('b', null), (null, 2));
+create table mclparted_p5 partition of mclparted for values in ((null, null));
+create table mclparted_p6 partition of mclparted DEFAULT;
+\d+ mclparted
+                           Partitioned table "public.mclparted"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | integer |           |          |         | plain    |              | 
+Partition key: LIST (a, b)
+Partitions: mclparted_p1 FOR VALUES IN (('a', 1)),
+            mclparted_p2 FOR VALUES IN (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3)),
+            mclparted_p3 FOR VALUES IN (('a', 3), ('a', 4), ('a', NULL), (NULL, 1)),
+            mclparted_p4 FOR VALUES IN (('b', NULL), (NULL, 2)),
+            mclparted_p5 FOR VALUES IN ((NULL, NULL)),
+            mclparted_p6 DEFAULT
+
+\d+ mclparted_p1
+                                Table "public.mclparted_p1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | integer |           |          |         | plain    |              | 
+Partition of: mclparted FOR VALUES IN (('a', 1))
+Partition constraint: (((a = 'a'::text) AND (b = 1)))
+
+\d+ mclparted_p2
+                                Table "public.mclparted_p2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | integer |           |          |         | plain    |              | 
+Partition of: mclparted FOR VALUES IN (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3))
+Partition constraint: (((a = 'a'::text) AND (b = 2)) OR ((a = 'b'::text) AND (b = 1)) OR ((a = 'c'::text) AND (b = 3)) OR ((a = 'd'::text) AND (b = 3)) OR ((a = 'e'::text) AND (b = 3)))
+
+\d+ mclparted_p3
+                                Table "public.mclparted_p3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | integer |           |          |         | plain    |              | 
+Partition of: mclparted FOR VALUES IN (('a', 3), ('a', 4), ('a', NULL), (NULL, 1))
+Partition constraint: (((a = 'a'::text) AND (b = 3)) OR ((a = 'a'::text) AND (b = 4)) OR ((a = 'a'::text) AND (b IS NULL)) OR ((a IS NULL) AND (b = 1)))
+
+\d+ mclparted_p4
+                                Table "public.mclparted_p4"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | integer |           |          |         | plain    |              | 
+Partition of: mclparted FOR VALUES IN (('b', NULL), (NULL, 2))
+Partition constraint: (((a = 'b'::text) AND (b IS NULL)) OR ((a IS NULL) AND (b = 2)))
+
+\d+ mclparted_p5
+                                Table "public.mclparted_p5"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ a      | text    |           |          |         | extended |              | 
+ b      | integer |           |          |         | plain    |              | 
+Partition of: mclparted FOR VALUES IN ((NULL, NULL))
+Partition constraint: (((a IS NULL) AND (b IS NULL)))
+
+insert into mclparted values ('a', 1), ('a', 2), ('b', 1), ('c', 3), ('d', 3),
+	('e', 3), ('a', 3), ('a', 4), ('a', null), (null, 1), ('b', null),
+	(null, 2), (null, null), ('z', 10);
+select tableoid::regclass, * from mclparted order by a, b;
+   tableoid   | a | b  
+--------------+---+----
+ mclparted_p1 | a |  1
+ mclparted_p2 | a |  2
+ mclparted_p3 | a |  3
+ mclparted_p3 | a |  4
+ mclparted_p3 | a |   
+ mclparted_p2 | b |  1
+ mclparted_p4 | b |   
+ mclparted_p2 | c |  3
+ mclparted_p2 | d |  3
+ mclparted_p2 | e |  3
+ mclparted_p6 | z | 10
+ mclparted_p3 |   |  1
+ mclparted_p4 |   |  2
+ mclparted_p5 |   |   
+(14 rows)
+
+drop table mclparted;
 -- check that wholerow vars in the RETURNING list work with partitioned tables
 create table returningwrtest (a int) partition by list (a);
 create table returningwrtest1 partition of returningwrtest for values in (1);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 54cbf6c..8462ff0 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -342,12 +342,6 @@ CREATE TABLE partitioned (
 	a int
 ) INHERITS (some_table) PARTITION BY LIST (a);
 
--- cannot use more than 1 column as partition key for list partitioned table
-CREATE TABLE partitioned (
-	a1 int,
-	a2 int
-) PARTITION BY LIST (a1, a2);	-- fail
-
 -- unsupported constraint type for partitioned tables
 CREATE TABLE partitioned (
 	a int,
@@ -725,6 +719,25 @@ CREATE TABLE range3_default PARTITION OF range_parted3 DEFAULT;
 -- more specific ranges
 CREATE TABLE fail_part PARTITION OF range_parted3 FOR VALUES FROM (1, minvalue) TO (1, maxvalue);
 
+-- now check for multi-column list partition key
+CREATE TABLE list_parted3 (
+	a int,
+	b varchar
+) PARTITION BY LIST (a, b);
+
+CREATE TABLE list_parted3_p1 PARTITION OF list_parted3 FOR VALUES IN ((1, 'A'));
+CREATE TABLE list_parted3_p2 PARTITION OF list_parted3 FOR VALUES IN ((1, 'B'),(1, 'E'), (1, 'E'), (2, 'C'),(2, 'D'));
+CREATE TABLE list_parted3_p3 PARTITION OF list_parted3 FOR VALUES IN ((1, NULL),(NULL, 'F'));
+CREATE TABLE list_parted3_p4 PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL));
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, 'E'));
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((1, NULL));
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, 'F'));
+CREATE TABLE fail_part PARTITION OF list_parted3 FOR VALUES IN ((NULL, NULL));
+CREATE TABLE list_parted3_default PARTITION OF list_parted3 DEFAULT;
+
+-- cleanup
+DROP TABLE list_parted3;
+
 -- check for partition bound overlap and other invalid specifications for the hash partition
 CREATE TABLE hash_parted2 (
 	a varchar
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index bfaa8a3..76e0d00 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -536,6 +536,48 @@ select tableoid::regclass::text, * from mcrparted order by 1;
 -- cleanup
 drop table mcrparted;
 
+-- Test multi-column list partitioning with 3 partition keys
+create table mclparted (a int, b text, c int) partition by list (a, b, c);
+create table mclparted_p1 partition of mclparted for values in ((1, 'a', 1));
+create table mclparted_p2 partition of mclparted for values in ((1, 'a', 2), (1, 'b', 1), (2, 'a', 1));
+create table mclparted_p3 partition of mclparted for values in ((3, 'c', 3), (4, 'd', 4), (5, 'e', 5), (6, null, 6));
+create table mclparted_p4 partition of mclparted for values in ((null, 'a', 1), (1, null, 1), (1, 'a', null));
+create table mclparted_p5 partition of mclparted for values in ((null, null, null));
+
+-- routed to mclparted_p1
+insert into mclparted values (1, 'a', 1);
+
+-- routed to mclparted_p2
+insert into mclparted values (1, 'a', 2);
+insert into mclparted values (1, 'b', 1);
+insert into mclparted values (2, 'a', 1);
+
+-- routed to mclparted_p3
+insert into mclparted values (3, 'c', 3);
+insert into mclparted values (4, 'd', 4);
+insert into mclparted values (5, 'e', 5);
+insert into mclparted values (6, null, 6);
+
+-- routed to mclparted_p4
+insert into mclparted values (null, 'a', 1);
+insert into mclparted values (1, null, 1);
+insert into mclparted values (1, 'a', null);
+
+-- routed to mclparted_p5
+insert into mclparted values (null, null, null);
+
+-- error cases
+insert into mclparted values (10, 'a', 1);
+insert into mclparted values (1, 'z', 1);
+insert into mclparted values (1, 'a', 10);
+insert into mclparted values (1, null, null);
+
+-- check rows
+select tableoid::regclass::text, * from mclparted order by 1;
+
+-- cleanup
+drop table mclparted;
+
 -- check that a BR constraint can't make partition contain violating rows
 create table brtrigpartcon (a int, b text) partition by list (a);
 create table brtrigpartcon1 partition of brtrigpartcon for values in (1);
@@ -612,6 +654,28 @@ insert into mcrparted values ('aaa', 0), ('b', 0), ('bz', 10), ('c', -10),
 select tableoid::regclass, * from mcrparted order by a, b;
 drop table mcrparted;
 
+-- check multi-column list partitioning with partition key constraint
+create table mclparted (a text, b int) partition by list(a, b);
+create table mclparted_p1 partition of mclparted for values in (('a', 1));
+create table mclparted_p2 partition of mclparted for values in (('a', 2), ('b', 1), ('c', 3), ('d', 3), ('e', 3));
+create table mclparted_p3 partition of mclparted for values in (('a', 3), ('a', 4), ('a', null), (null, 1));
+create table mclparted_p4 partition of mclparted for values in (('b', null), (null, 2));
+create table mclparted_p5 partition of mclparted for values in ((null, null));
+create table mclparted_p6 partition of mclparted DEFAULT;
+
+\d+ mclparted
+\d+ mclparted_p1
+\d+ mclparted_p2
+\d+ mclparted_p3
+\d+ mclparted_p4
+\d+ mclparted_p5
+
+insert into mclparted values ('a', 1), ('a', 2), ('b', 1), ('c', 3), ('d', 3),
+	('e', 3), ('a', 3), ('a', 4), ('a', null), (null, 1), ('b', null),
+	(null, 2), (null, null), ('z', 10);
+select tableoid::regclass, * from mclparted order by a, b;
+drop table mclparted;
+
 -- check that wholerow vars in the RETURNING list work with partitioned tables
 create table returningwrtest (a int) partition by list (a);
 create table returningwrtest1 partition of returningwrtest for values in (1);
