From 199b87deb39c393562d96dec56c565e25ae76edc Mon Sep 17 00:00:00 2001
From: "Andrey M. Borodin" <x4mmm@night.Dlink>
Date: Thu, 2 May 2024 22:28:15 +0500
Subject: [PATCH v1] Add URLs to \h in psql when there is no match

---
 src/bin/psql/help.c     | 44 ++++++++++++++++++++++++++++++++++++++++-
 src/bin/psql/settings.h |  3 +++
 src/bin/psql/startup.c  | 11 +++++++++++
 3 files changed, 57 insertions(+), 1 deletion(-)

diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 4e79a819d8..703597289e 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -422,6 +422,8 @@ helpVariables(unsigned short int pager)
 		  "    specifies the prompt used when a statement continues from a previous line\n");
 	HELP0("  PROMPT3\n"
 		  "    specifies the prompt used during COPY ... FROM STDIN\n");
+	HELP0("  HELP_URLS\n"
+		  "    specifies |-separated array of URLs with \%s\n");
 	HELP0("  QUIET\n"
 		  "    run quietly (same as -q option)\n");
 	HELP0("  ROW_COUNT\n"
@@ -558,6 +560,30 @@ helpVariables(unsigned short int pager)
 }
 
 
+static char *url_encode(const char *input) {
+	/* Allocate enough space for the encoded string */
+	char *output = calloc(strlen(input) * 3 + 1, sizeof(char));
+	int output_pos = 0;
+
+	for (int i = 0; input[i]; i++) {
+		unsigned char c = input[i];
+		if (c == ' ') {
+			output[output_pos++] = '%';
+			output[output_pos++] = '2';
+			output[output_pos++] = '0';
+		} else if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') {
+			output[output_pos++] = c;
+		} else {
+			output[output_pos++] = '%';
+			output[output_pos++] = "0123456789ABCDEF"[c >> 4];
+			output[output_pos++] = "0123456789ABCDEF"[c & 0xF];
+		}
+	}
+
+	return output;
+}
+
+
 /*
  * helpSQL -- help with SQL commands
  *
@@ -713,10 +739,26 @@ helpSQL(const char *topic, unsigned short int pager)
 		/* If we never found anything, report that */
 		if (!output)
 		{
+			char* urls;
+			char* url;
+			char* encoded_topic;
 			output = PageOutput(2, pager ? &(pset.popt.topt) : NULL);
 			fprintf(output, _("No help available for \"%s\".\n"
-							  "Try \\h with no arguments to see available help.\n"),
+							  "Try \\h with no arguments to see available help or search online:\n"),
 					topic);
+			/* TODO: URL encode topic*/
+			encoded_topic = url_encode(topic);
+			urls = pstrdup(pset.help_urls);
+			url = strtok(urls,"|");
+			while (url != NULL)
+			{
+				fprintf(output, "\nURL: ");
+				fprintf(output, url, encoded_topic);
+				fprintf(output, "\n");
+				url = strtok(NULL, "|");
+			}
+			fprintf(output, "\n");
+			free(encoded_topic);
 		}
 
 		ClosePager(output);
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 505f99d8e4..0be1300738 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -27,6 +27,8 @@
 #define DEFAULT_PROMPT2 "%/%R%x%# "
 #define DEFAULT_PROMPT3 ">> "
 
+#define DEFAULT_HELP_URLS "https://www.postgresql.org/search/?q=%s"
+
 /*
  * Note: these enums should generally be chosen so that zero corresponds
  * to the default behavior.
@@ -151,6 +153,7 @@ typedef struct _psqlSettings
 	const char *prompt1;
 	const char *prompt2;
 	const char *prompt3;
+	const char *help_urls;
 	PGVerbosity verbosity;		/* current error verbosity level */
 	bool		show_all_results;
 	PGContextVisibility show_context;	/* current context display level */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 036caaec2f..824297fd57 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -203,6 +203,7 @@ main(int argc, char *argv[])
 	SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
 	SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
 	SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
+	SetVariable(pset.vars, "HELP_URLS", DEFAULT_HELP_URLS);
 	SetVariableBool(pset.vars, "SHOW_ALL_RESULTS");
 
 	parse_psql_options(argc, argv, &options);
@@ -1115,6 +1116,13 @@ prompt3_hook(const char *newval)
 	return true;
 }
 
+static bool
+help_urls_hook(const char *newval)
+{
+	pset.help_urls = newval ? newval : "";
+	return true;
+}
+
 static char *
 verbosity_substitute_hook(char *newval)
 {
@@ -1250,6 +1258,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "PROMPT3",
 					 NULL,
 					 prompt3_hook);
+	SetVariableHooks(pset.vars, "HELP_URLS",
+					 NULL,
+					 help_urls_hook);
 	SetVariableHooks(pset.vars, "VERBOSITY",
 					 verbosity_substitute_hook,
 					 verbosity_hook);
-- 
2.37.1 (Apple Git-137.1)

