diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56ff6..4adb334470c 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -164,6 +164,7 @@ bool		enable_parallel_hash = true;
 bool		enable_partition_pruning = true;
 bool		enable_presorted_aggregate = true;
 bool		enable_async_append = true;
+bool		enable_starjoin_ordering = true;
 
 typedef struct
 {
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 443e2dca7c0..24c00c03ebb 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -32,6 +32,9 @@ static void make_rels_by_clause_joins(PlannerInfo *root,
 static void make_rels_by_clauseless_joins(PlannerInfo *root,
 										  RelOptInfo *old_rel,
 										  List *other_rels);
+static bool starjoin_order_invalid(PlannerInfo *root,
+								   RelOptInfo *rel1,
+								   RelOptInfo *rel2);
 static bool has_join_restriction(PlannerInfo *root, RelOptInfo *rel);
 static bool has_legal_joinclause(PlannerInfo *root, RelOptInfo *rel);
 static bool restriction_is_constant_false(List *restrictlist,
@@ -709,6 +712,14 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
 	/* We should never try to join two overlapping sets of rels. */
 	Assert(!bms_overlap(rel1->relids, rel2->relids));
 
+	/*
+	 * If this join would be a redundant ordering of a previously detected
+	 * starjoin cluster, skip it. All cluster orderings have identical cost;
+	 * we keep only the canonical one chosen when building starjoin clusters.
+	 */
+	if (starjoin_order_invalid(root, rel1, rel2))
+		return NULL;
+
 	/* Construct Relids set that identifies the joinrel (without OJ as yet). */
 	joinrelids = bms_union(rel1->relids, rel2->relids);
 
@@ -2112,3 +2123,107 @@ get_matching_part_pairs(PlannerInfo *root, RelOptInfo *joinrel,
 		*parts2 = lappend(*parts2, child_rel2);
 	}
 }
+
+/*
+ * starjoin_order_invalid
+ *		Return true if the proposed join (rel1, rel2) would build a
+ *		non-canonical ordering of a starjoin cluster.
+ *
+ * For every star cluster in root->starjoin_clusters, we enforce the canonical
+ * decomposition of any joinrel containinig the fact and at least one cluster
+ * dimension - the dimensions must form a prefix of the order determined when
+ * collecting the starjoin clusters.
+ *
+ * This rule preserves exactly one decomposition per joinrel relid set, so
+ * each canonical joinrel is still built; redundant orderings are pruned.
+ *
+ * We might forbid additional joins, for example we might forbid joinrels
+ * containing a cluster dimension without the fact.
+ */
+static bool
+starjoin_order_invalid(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
+{
+	ListCell   *lc;
+	Relids		join_relids;
+	bool		violates = false;
+
+	/* No starjoin optimization outside the stardard DP join search. */
+	if (root->join_rel_level == NULL)
+		return false;
+
+	/* no starjoins found (maybe because the option is disabled) */
+	if (root->starjoin_clusters == NIL)
+		return false;
+
+	/* relids of the result */
+	join_relids = bms_union(rel1->relids, rel2->relids);
+
+	/* does this contradict any of the clusters we know about */
+	foreach(lc, root->starjoin_clusters)
+	{
+		StarJoinClusterInfo *c = (StarJoinClusterInfo *) lfirst(lc);
+		Relids	dims;
+		int		ndims;
+		int		idx;
+
+		/*
+		 * If all dimensions are on one side of the join, we've already
+		 * checked the canonical order earlier, when building it. No need
+		 * to redo the check again.
+		 */
+		dims = bms_intersect(rel1->relids, c->dim_relids);
+		if (bms_is_empty(dims))
+		{
+			bms_free(dims);
+			continue;
+		}
+		bms_free(dims);
+
+		dims = bms_intersect(rel2->relids, c->dim_relids);
+		if (bms_is_empty(dims))
+		{
+			bms_free(dims);
+			continue;
+		}
+		bms_free(dims);
+
+		/* the canonical ordering has to include the fact */
+		if (!bms_is_member(c->fact_relid, join_relids))
+		{
+			violates = true;
+			break;
+		}
+
+		/*
+		 * Count the prefix length covered by this join. We simply use the
+		 * bitmap of cluster dimensions. For now that's fine, but it we
+		 * choose to allow snowflake joins, we'll need to rethink it and use
+		 * a list or something where the order is not determined by relid
+		 * (probably?)
+		 */
+		ndims = 0;
+		idx = -1;
+		while ((idx = bms_next_member(c->dim_relids, idx)) >= 0)
+		{
+			/* found end of dimension prefix */
+			if (!bms_is_member(idx, join_relids))
+				break;
+
+			ndims++;
+		}
+
+		dims = bms_intersect(join_relids, c->dim_relids);
+		if (bms_num_members(dims) != ndims)
+		{
+			bms_free(dims);
+			violates = true;
+			break;
+		}
+
+		bms_free(dims);
+	}
+
+	bms_free(join_relids);
+
+	return violates;
+}
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index b38422c47a4..4dbc0512d4e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -4295,3 +4295,260 @@ check_memoizable(RestrictInfo *restrictinfo)
 	if (OidIsValid(typentry->hash_proc) && OidIsValid(typentry->eq_opr))
 		restrictinfo->right_hasheqoperator = typentry->eq_opr;
 }
+
+
+
+/*
+ * starjoins_canonicalize
+ *		Detect star-shaped sub-joins and determine canonical join order.
+ *
+ * A starjoin cluster is a fact relation joined to two or more dimensions
+ * via an equality condition matching a foreign key, satisfying these
+ * conditions:
+ *
+ *	1. The dimension is a plain base relation (RELOPT_BASEREL).
+ *
+ *	2. All join columns on the fact are NOT NULL.
+ *
+ *	3. The dimension has no local restriction quals (baserestrictinfo).
+ *
+ *	4. The dimension has no relevant join clauses to any rel other than
+ *	   the fact (checked via have_relevant_joinclause, which also covers
+ *	   equivalence-class joins).
+ *
+ *	5. The dimension does not participate in any SpecialJoinInfo.
+ *
+ *	6. The dimension has no lateral references in or out.
+ *
+ *	7. The dimension is not constrained by any PlaceHolderInfo.
+ *
+ * Under these conditions, the join is strictly cardinality-preserving with
+ * respect to the fact table. And all join orderings for a cluster produce
+ * identical row counts and identical per-step costs. The join_search can
+ * therefore consider only a single canonical ordering without losing the
+ * optimal plan.
+ *
+ * Dimensions that fail the preconditions are simply not added to any
+ * cluster; the planner then handles them in the ordinary way.  A fact
+ * with fewer than two qualifying dimensions produces no cluster (there
+ * is nothing to deduplicate).
+ *
+ * Must be called after match_foreign_keys_to_quals(), and after lateral,
+ * PHV and SpecialJoinInfo information have been finalized.
+ */
+void
+generate_starjoin_clusters(PlannerInfo *root)
+{
+	int			nrels;
+	List	  **fact_dims;
+	int			f;
+	ListCell   *lc;
+
+	/* do nothing if optimization not enabled */
+	if (!enable_starjoin_ordering)
+		return;
+
+	/* also, do nothing if there are no foreign key joins */
+	if (root->fkey_list == NIL)
+		return;
+
+	nrels = root->simple_rel_array_size;
+	fact_dims = (List **) palloc0(nrels * sizeof(List *));
+
+	/*
+	 * Group fully-matched FKs by referencing (fact) relid, collecting
+	 * candidate dimension relids. match_foreign_keys_to_quals() has
+	 * already filtered fkey_list to fully-matched FKs.
+	 */
+	foreach(lc, root->fkey_list)
+	{
+		ForeignKeyOptInfo *fkinfo = (ForeignKeyOptInfo *) lfirst(lc);
+		Index		fact_rti = fkinfo->con_relid;
+		Index		dim_rti = fkinfo->ref_relid;
+
+		/*
+		 * Expect valid RTI on both sides (otherwise should not have kept
+		 * this foreign key. Similarly, we shouldn't get a FK with the
+		 * same RTI on both ends.
+		 */
+		Assert(fact_rti < root->simple_rel_array_size);
+		Assert(dim_rti < root->simple_rel_array_size);
+		Assert(fact_rti != dim_rti);
+
+		/*
+		 * With duplicate foreign keys we might have already included this
+		 * candidate dimension.
+		 */
+		if (list_member_int(fact_dims[fact_rti], dim_rti))
+			continue;
+
+		/* found a candidate dimension, stash it to the list */
+		fact_dims[fact_rti] = lappend_int(fact_dims[fact_rti], dim_rti);
+	}
+
+	/*
+	 * For each candidate fact, validate which dimensions meet the other
+	 * requirements, and build a StarJoinClusterInfo if at least two
+	 * dimensions qualify.
+	 */
+	for (f = 1; f < nrels; f++)
+	{
+		RelOptInfo *fact_rel;
+		Relids		dim_relids = NULL;
+		int			ndims = 0;
+		ListCell   *lc2;
+		StarJoinClusterInfo *cluster;
+
+		/*
+		 * Can't be a fact if there are no potential dimensions (we need
+		 * at least two for a starjoin optimization to matter).
+		 */
+		if (list_length(fact_dims[f]) < 2)
+			continue;
+
+		/* has to be a baserel (how else could it be, if it has a FK?) */
+		fact_rel = root->simple_rel_array[f];
+		if (fact_rel == NULL || fact_rel->reloptkind != RELOPT_BASEREL)
+			continue;
+
+		/* count dimensions matching the requirements */
+		foreach(lc2, fact_dims[f])
+		{
+			Index		d = (Index) lfirst_int(lc2);
+			RelOptInfo *dim_rel = root->simple_rel_array[d];
+			ForeignKeyOptInfo *fkinfo = NULL;
+			ListCell   *lc3;
+			bool		ok = true;
+			int			colno;
+			int			r;
+
+			/* has to be a baserel (it's referenced by a FK, so?) */
+			if (dim_rel == NULL || dim_rel->reloptkind != RELOPT_BASEREL)
+				continue;
+
+			/* No local restriction quals on the dimension. */
+			if (dim_rel->baserestrictinfo != NIL)
+				continue;
+
+			/* No lateral references in or out. */
+			if (!bms_is_empty(dim_rel->lateral_relids))
+				continue;
+			if (!bms_is_empty(dim_rel->lateral_referencers))
+				continue;
+
+			/* Find the matching FK so we can check NOT NULL on conkey cols */
+			foreach(lc3, root->fkey_list)
+			{
+				ForeignKeyOptInfo *fki = (ForeignKeyOptInfo *) lfirst(lc3);
+
+				if ((fki->con_relid == f) && (fki->ref_relid == d))
+				{
+					fkinfo = fki;
+					break;
+				}
+			}
+
+			/* we should have found a FK, that's how we got the array */
+			Assert(fkinfo != NULL);
+
+			/*
+			 * Make sure all columns on the fact side are NOT NULL. We
+			 * know the FK is fully matched by the join, so use that.
+			 */
+			for (colno = 0; colno < fkinfo->nkeys; colno++)
+			{
+				if (!bms_is_member(fkinfo->conkey[colno],
+								   fact_rel->notnullattnums))
+				{
+					ok = false;
+					break;
+				}
+			}
+			if (!ok)
+				continue;
+
+			/*
+			 * Should not be involved in any SpecialJoinInfo (for now the
+			 * starjoin is done only for inner joins).
+			 *
+			 * XXX We could relax this, and allow left joins, as long as the
+			 * target has a unique constraint.
+			 */
+			foreach(lc3, root->join_info_list)
+			{
+				SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc3);
+
+				if (bms_is_member(d, sjinfo->min_lefthand) ||
+					bms_is_member(d, sjinfo->min_righthand) ||
+					bms_is_member(d, sjinfo->syn_lefthand) ||
+					bms_is_member(d, sjinfo->syn_righthand))
+				{
+					ok = false;
+					break;
+				}
+			}
+			if (!ok)
+				continue;
+
+			/* Not constrained by any PlaceHolderInfo. */
+			foreach(lc3, root->placeholder_list)
+			{
+				PlaceHolderInfo *phi = (PlaceHolderInfo *) lfirst(lc3);
+
+				if (bms_is_member((int) d, phi->ph_eval_at) ||
+					bms_is_member((int) d, phi->ph_lateral) ||
+					bms_is_member((int) d, phi->ph_needed))
+				{
+					ok = false;
+					break;
+				}
+			}
+			if (!ok)
+				continue;
+
+			/*
+			 * The dimension must have no relevant join clause to any rel
+			 * other than the fact.  have_relevant_joinclause() also covers
+			 * equivalence-class joins.
+			 */
+			for (r = 1; r < nrels; r++)
+			{
+				RelOptInfo *other_rel;
+
+				if (r == (int) d || r == f)
+					continue;
+				other_rel = root->simple_rel_array[r];
+				if (other_rel == NULL ||
+					other_rel->reloptkind != RELOPT_BASEREL)
+					continue;
+				if (have_relevant_joinclause(root, dim_rel, other_rel))
+				{
+					ok = false;
+					break;
+				}
+			}
+			if (!ok)
+				continue;
+
+			/* Qualifies as a dimension. */
+			dim_relids = bms_add_member(dim_relids, (int) d);
+			ndims++;
+		}
+
+		/* we need at least 2 dimensions for this optimization to matter */
+		if (ndims < 2)
+		{
+			bms_free(dim_relids);
+			continue;
+		}
+
+		/* remember this cluster */
+		cluster = makeNode(StarJoinClusterInfo);
+		cluster->fact_relid = (Index) f;
+		cluster->dim_relids = dim_relids;
+
+		root->starjoin_clusters = lappend(root->starjoin_clusters, cluster);
+	}
+
+	pfree(fact_dims);
+}
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 02495e22e24..5734aecfd98 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -80,6 +80,7 @@ query_planner(PlannerInfo *root,
 	root->group_expr_list = NIL;
 	root->tlist_vars = NIL;
 	root->fkey_list = NIL;
+	root->starjoin_clusters = NIL;
 	root->initial_rels = NIL;
 
 	/*
@@ -262,6 +263,14 @@ query_planner(PlannerInfo *root,
 	 */
 	match_foreign_keys_to_quals(root);
 
+	/*
+	 * Detect star-shaped sub-joins so that join_search can skip enumerating
+	 * equivalent join orderings.  Must be done after foreign keys are
+	 * matched to quals and after lateral, PHV and SpecialJoinInfo info is
+	 * available.
+	 */
+	generate_starjoin_clusters(root);
+
 	/*
 	 * Look for join OR clauses that we can extract single-relation
 	 * restriction OR clauses from.
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index afaa058b046..f175611a6d9 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1034,6 +1034,14 @@
   boot_val => 'true',
 },
 
+{ name => 'enable_starjoin_ordering', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
+  short_desc => 'Enables collapsing of redundant join orderings in star-shaped queries.',
+  long_desc => 'When enabled, the planner detects star joins (a fact table joined to multiple dimension tables via foreign keys referencing unique keys, with NOT NULL FK columns and no dimension-side restrictions) and avoids enumerating equivalent join orderings, since they have identical cost.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'enable_starjoin_ordering',
+  boot_val => 'true',
+},
+
 { name => 'enable_tidscan', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
   short_desc => 'Enables the planner\'s use of TID scan plans.',
   flags => 'GUC_EXPLAIN',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ac38cddaaf9..9e8812b64f5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -448,6 +448,7 @@
 #enable_distinct_reordering = on
 #enable_self_join_elimination = on
 #enable_eager_aggregate = on
+#enable_starjoin_ordering = on
 
 # - Planner Cost Constants -
 
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c6815b7..4e39c906ccd 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -512,6 +512,14 @@ struct PlannerInfo
 	/* list of ForeignKeyOptInfos */
 	List	   *fkey_list;
 
+	/*
+	 * List of StarJoinClusterInfo entries describing detected star-shaped
+	 * sub-joins (a fact table joined to multiple FK-unique dimensions).
+	 * Used by join_search to skip enumeration of equivalent join orderings.
+	 * Populated by identify_star_join_clusters().  Not serialized.
+	 */
+	List	   *starjoin_clusters pg_node_attr(read_write_ignore);
+
 	/* desired pathkeys for query_planner() */
 	List	   *query_pathkeys;
 
@@ -1500,6 +1508,34 @@ typedef struct ForeignKeyOptInfo
 	List	   *rinfos[INDEX_MAX_KEYS];
 } ForeignKeyOptInfo;
 
+/*
+ * StarJoinClusterInfo
+ *		Description of a star-shaped sub-join detected by the planner.
+ *
+ * A "star cluster" consists of a single "fact" base relation joined to a
+ * set of "dimension" base relations.  For the cluster to be recognized,
+ * each dimension must be joined to the fact via an equality condition that
+ * matches a foreign key on the fact referencing the dimension. The columns
+ * on the fact must all be NOT NULL, and the dimension must have no other
+ * join clauses, no local restriction quals, no participation in special
+ * joins or lateral references, etc. (see generate_starjoin_cluster for
+ * the full list of preconditions).
+ *
+ * When all of these conditions hold, every join ordering that has the fact
+ * as the driver produces exactly the same row count and the same per-step
+ * cost.  join_search can therefore consider just one canonical ordering of
+ * the cluster without compromising plan quality.
+ *
+ * The bimap of dimensions determines the canonical join order.
+ */
+typedef struct StarJoinClusterInfo
+{
+	NodeTag		type;
+	Index		fact_relid;	/* index of the fact relation */
+	Relids		dim_relids;	/* indexes of the dimension relations */
+} StarJoinClusterInfo;
+
+
 /*
  * StatisticExtInfo
  *		Information about extended statistics for planning/optimization
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index f2fd5d31507..766aba773ef 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -70,6 +70,7 @@ extern PGDLLIMPORT bool enable_parallel_hash;
 extern PGDLLIMPORT bool enable_partition_pruning;
 extern PGDLLIMPORT bool enable_presorted_aggregate;
 extern PGDLLIMPORT bool enable_async_append;
+extern PGDLLIMPORT bool enable_starjoin_ordering;
 extern PGDLLIMPORT int constraint_exclusion;
 
 extern double index_pages_fetched(double tuples_fetched, BlockNumber pages,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 71c043a25e8..1c8e9ef038a 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -104,6 +104,7 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root,
 												 Index security_level);
 extern void rebuild_joinclause_attr_needed(PlannerInfo *root);
 extern void match_foreign_keys_to_quals(PlannerInfo *root);
+extern void generate_starjoin_clusters(PlannerInfo *root);
 
 /*
  * prototypes for plan/analyzejoins.c
diff --git a/src/test/modules/unsafe_tests/Makefile b/src/test/modules/unsafe_tests/Makefile
index a85c854392d..dcfac290eaa 100644
--- a/src/test/modules/unsafe_tests/Makefile
+++ b/src/test/modules/unsafe_tests/Makefile
@@ -1,6 +1,6 @@
 # src/test/modules/unsafe_tests/Makefile
 
-REGRESS = rolenames setconfig alter_system_table guc_privs
+REGRESS = setconfig alter_system_table guc_privs
 REGRESS_OPTS = \
 	--create-role=regress_authenticated_user_db_sr \
 	--create-role=regress_authenticated_user_db_ssa \
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 78bf022f7b4..8b62f343200 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -10143,3 +10143,60 @@ SELECT COUNT(*) FROM onek t1 LEFT JOIN tenk1 t2
  19000
 (1 row)
 
+--
+-- Star-join collapse: FK-unique star clusters get a canonical join order
+--
+CREATE TABLE sj_d1 (id int PRIMARY KEY, v text);
+CREATE TABLE sj_d2 (id int PRIMARY KEY, v text);
+CREATE TABLE sj_d3 (id int PRIMARY KEY, v text);
+CREATE TABLE sj_f (
+    id int PRIMARY KEY,
+    d1 int NOT NULL REFERENCES sj_d1,
+    d2 int NOT NULL REFERENCES sj_d2,
+    d3 int NOT NULL REFERENCES sj_d3
+);
+ANALYZE sj_f, sj_d1, sj_d2, sj_d3;
+-- With the collapse enabled (default), the resulting plan must be identical
+-- to the plan produced with the optimization disabled.
+SET enable_starjoin_ordering = on;
+EXPLAIN (COSTS OFF)
+SELECT * FROM sj_f
+  JOIN sj_d1 ON sj_f.d1 = sj_d1.id
+  JOIN sj_d2 ON sj_f.d2 = sj_d2.id
+  JOIN sj_d3 ON sj_f.d3 = sj_d3.id;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   Join Filter: (sj_d3.id = sj_f.d3)
+   ->  Nested Loop
+         Join Filter: (sj_d2.id = sj_f.d2)
+         ->  Nested Loop
+               Join Filter: (sj_d1.id = sj_f.d1)
+               ->  Seq Scan on sj_f
+               ->  Seq Scan on sj_d1
+         ->  Seq Scan on sj_d2
+   ->  Seq Scan on sj_d3
+(10 rows)
+
+SET enable_starjoin_ordering = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM sj_f
+  JOIN sj_d1 ON sj_f.d1 = sj_d1.id
+  JOIN sj_d2 ON sj_f.d2 = sj_d2.id
+  JOIN sj_d3 ON sj_f.d3 = sj_d3.id;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Nested Loop
+   Join Filter: (sj_d3.id = sj_f.d3)
+   ->  Nested Loop
+         Join Filter: (sj_d2.id = sj_f.d2)
+         ->  Nested Loop
+               Join Filter: (sj_d1.id = sj_f.d1)
+               ->  Seq Scan on sj_f
+               ->  Seq Scan on sj_d1
+         ->  Seq Scan on sj_d2
+   ->  Seq Scan on sj_d3
+(10 rows)
+
+RESET enable_starjoin_ordering;
+DROP TABLE sj_f, sj_d1, sj_d2, sj_d3;
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 132b56a5864..ca6c1acd00c 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -179,8 +179,9 @@ select name, setting from pg_settings where name like 'enable%';
  enable_self_join_elimination   | on
  enable_seqscan                 | on
  enable_sort                    | on
+ enable_starjoin_ordering       | on
  enable_tidscan                 | on
-(25 rows)
+(26 rows)
 
 -- There are always wait event descriptions for various types.  InjectionPoint
 -- may be present or absent, depending on history since last postmaster start.
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index fae19113cef..ade308aca56 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -3891,3 +3891,36 @@ SELECT COUNT(*) FROM onek t1 LEFT JOIN tenk1 t2
     ON (t2.thousand = t1.tenthous OR t2.thousand = t1.thousand);
 SELECT COUNT(*) FROM onek t1 LEFT JOIN tenk1 t2
     ON (t2.thousand = t1.tenthous OR t2.thousand = t1.thousand);
+
+--
+-- Star-join collapse: FK-unique star clusters get a canonical join order
+--
+CREATE TABLE sj_d1 (id int PRIMARY KEY, v text);
+CREATE TABLE sj_d2 (id int PRIMARY KEY, v text);
+CREATE TABLE sj_d3 (id int PRIMARY KEY, v text);
+CREATE TABLE sj_f (
+    id int PRIMARY KEY,
+    d1 int NOT NULL REFERENCES sj_d1,
+    d2 int NOT NULL REFERENCES sj_d2,
+    d3 int NOT NULL REFERENCES sj_d3
+);
+ANALYZE sj_f, sj_d1, sj_d2, sj_d3;
+
+-- With the collapse enabled (default), the resulting plan must be identical
+-- to the plan produced with the optimization disabled.
+SET enable_starjoin_ordering = on;
+EXPLAIN (COSTS OFF)
+SELECT * FROM sj_f
+  JOIN sj_d1 ON sj_f.d1 = sj_d1.id
+  JOIN sj_d2 ON sj_f.d2 = sj_d2.id
+  JOIN sj_d3 ON sj_f.d3 = sj_d3.id;
+
+SET enable_starjoin_ordering = off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM sj_f
+  JOIN sj_d1 ON sj_f.d1 = sj_d1.id
+  JOIN sj_d2 ON sj_f.d2 = sj_d2.id
+  JOIN sj_d3 ON sj_f.d3 = sj_d3.id;
+
+RESET enable_starjoin_ordering;
+DROP TABLE sj_f, sj_d1, sj_d2, sj_d3;
