From 2e612e565918608e192125830b821df5d1aa3c84 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 18 Sep 2024 10:13:19 -0400
Subject: [PATCH v3 3/3] New contrib module: hint_via_alias.

This forces a table to be merge joined, hash joined, or joined via a nested
loop if the table alias starts with mj_, hj_, or nl_, respectively. It
demonstrates that join_path_setup_hook is sufficient to control the join
method, and is not intended for commit.
---
 contrib/Makefile                        |   1 +
 contrib/hint_via_alias/Makefile         |  17 ++++
 contrib/hint_via_alias/hint_via_alias.c | 114 ++++++++++++++++++++++++
 contrib/hint_via_alias/meson.build      |  12 +++
 contrib/meson.build                     |   1 +
 5 files changed, 145 insertions(+)
 create mode 100644 contrib/hint_via_alias/Makefile
 create mode 100644 contrib/hint_via_alias/hint_via_alias.c
 create mode 100644 contrib/hint_via_alias/meson.build

diff --git a/contrib/Makefile b/contrib/Makefile
index b3422616698..2b47095ce18 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -22,6 +22,7 @@ SUBDIRS = \
 		earthdistance	\
 		file_fdw	\
 		fuzzystrmatch	\
+		hint_via_alias	\
 		hstore		\
 		intagg		\
 		intarray	\
diff --git a/contrib/hint_via_alias/Makefile b/contrib/hint_via_alias/Makefile
new file mode 100644
index 00000000000..2e0e540d352
--- /dev/null
+++ b/contrib/hint_via_alias/Makefile
@@ -0,0 +1,17 @@
+# contrib/hint_via_alias/Makefile
+
+MODULE_big = hint_via_alias
+OBJS = \
+	$(WIN32RES) \
+	hint_via_alias.o
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/hint_via_alias
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/hint_via_alias/hint_via_alias.c b/contrib/hint_via_alias/hint_via_alias.c
new file mode 100644
index 00000000000..fc0fbd85c22
--- /dev/null
+++ b/contrib/hint_via_alias/hint_via_alias.c
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ *
+ * hint_via_alias.c
+ *	  force tables to be joined in using a nestedloop, mergejoin, or hash
+ *    join if their alias name begins with nl_, mj_, or hj_.
+ *
+ * Copyright (c) 2016-2024, PostgreSQL Global Development Group
+ *
+ *	  contrib/hint_via_alias/hint_via_alias.c
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "optimizer/paths.h"
+#include "parser/parsetree.h"
+
+typedef enum
+{
+	HVA_UNSPECIFIED,
+	HVA_HASHJOIN,
+	HVA_MERGEJOIN,
+	HVA_NESTLOOP
+} hva_hint;
+
+static void hva_join_path_setup_hook(PlannerInfo *root,
+									 RelOptInfo *joinrel,
+									 RelOptInfo *outerrel,
+									 RelOptInfo *innerrel,
+									 JoinType jointype,
+									 JoinPathExtraData *extra);
+static hva_hint get_hint(PlannerInfo *root, Index relid);
+
+static join_path_setup_hook_type prev_join_path_setup_hook = NULL;
+
+PG_MODULE_MAGIC;
+
+void
+_PG_init(void)
+{
+	prev_join_path_setup_hook = join_path_setup_hook;
+	join_path_setup_hook = hva_join_path_setup_hook;
+}
+
+static void
+hva_join_path_setup_hook(PlannerInfo *root, RelOptInfo *joinrel,
+						 RelOptInfo *outerrel, RelOptInfo *innerrel,
+						 JoinType jointype, JoinPathExtraData *extra)
+{
+	hva_hint	outerhint = HVA_UNSPECIFIED;
+	hva_hint	innerhint = HVA_UNSPECIFIED;
+	hva_hint	hint;
+
+	if (outerrel->reloptkind == RELOPT_BASEREL ||
+		outerrel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+		outerhint = get_hint(root, outerrel->relid);
+
+	if (innerrel->reloptkind == RELOPT_BASEREL ||
+		innerrel->reloptkind == RELOPT_OTHER_MEMBER_REL)
+		innerhint = get_hint(root, innerrel->relid);
+
+	/*
+	 * If the hints conflict, that's not necessarily an indication of user error.
+	 * For example, if the user joins A to B and supplies different join method hints
+	 * for A and B, we will end up using a disabled path. However, if they are joining
+	 * A, B, and C and supply different join method hints for A and B, we could
+	 * potentially respect both hints by avoiding a direct A-B join altogether. Even if
+	 * it does turn out that we can't respect all the hints, we don't need any special
+	 * handling for that here: the planner will just return a disabled path.
+	 */
+	if (outerhint != HVA_UNSPECIFIED && innerhint != HVA_UNSPECIFIED &&
+		outerhint != innerhint)
+	{
+		extra->jsa_mask = 0;
+		return;
+	}
+
+	if (outerhint != HVA_UNSPECIFIED)
+		hint = outerhint;
+	else
+		hint = innerhint;
+
+	switch (hint)
+	{
+		case HVA_UNSPECIFIED:
+			break;
+		case HVA_NESTLOOP:
+			extra->jsa_mask &= JSA_NESTLOOP_ANY;
+			break;
+		case HVA_HASHJOIN:
+			extra->jsa_mask &= JSA_HASHJOIN;
+			break;
+		case HVA_MERGEJOIN:
+			extra->jsa_mask &= JSA_MERGEJOIN_ANY;
+			break;
+	}
+}
+
+static hva_hint
+get_hint(PlannerInfo *root, Index relid)
+{
+	RangeTblEntry *rte = planner_rt_fetch(relid, root);
+
+	Assert(rte->eref != NULL && rte->eref->aliasname != NULL);
+
+	if (strncmp(rte->eref->aliasname, "nl_", 3) == 0)
+		return HVA_NESTLOOP;
+	else if (strncmp(rte->eref->aliasname, "hj_", 3) == 0)
+		return HVA_HASHJOIN;
+	else if (strncmp(rte->eref->aliasname, "mj_", 3) == 0)
+		return HVA_MERGEJOIN;
+	else
+		return HVA_UNSPECIFIED;
+}
diff --git a/contrib/hint_via_alias/meson.build b/contrib/hint_via_alias/meson.build
new file mode 100644
index 00000000000..7e42c5783ab
--- /dev/null
+++ b/contrib/hint_via_alias/meson.build
@@ -0,0 +1,12 @@
+# Copyright (c) 2022-2024, PostgreSQL Global Development Group
+
+hint_via_alias_sources = files(
+  'hint_via_alias.c',
+)
+
+hint_via_alias = shared_module('hint_via_alias',
+  hint_via_alias_sources,
+  kwargs: contrib_mod_args,
+)
+
+contrib_targets += hint_via_alias
diff --git a/contrib/meson.build b/contrib/meson.build
index 4372242c8f3..261e4c480e2 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -30,6 +30,7 @@ subdir('dict_xsyn')
 subdir('earthdistance')
 subdir('file_fdw')
 subdir('fuzzystrmatch')
+subdir('hint_via_alias')
 subdir('hstore')
 subdir('hstore_plperl')
 subdir('hstore_plpython')
-- 
2.39.3 (Apple Git-145)

