diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 16999dd9a2..5658ec3759 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -110,6 +110,12 @@ typedef struct VersionedQuery
  * time.  So we put the components of each query into this struct and
  * assemble them with the common boilerplate in _complete_from_query().
  *
+ * We also use this struct to define queries that use completion_info_object,
+ * which is some object related to the one(s) we want to get the names of
+ * (for example, the table we want the indexes of).  In that usage the
+ * objects we're completing might not have a schema of their own, but the
+ * related object almost always does (passed in completion_info_schema).
+ *
  * As with VersionedQuery, we can use an array of these if the query details
  * must vary across versions.
  */
@@ -123,8 +129,9 @@ typedef struct SchemaQuery
 	int			min_server_version;
 
 	/*
-	 * Name of catalog or catalogs to be queried, with alias, eg.
-	 * "pg_catalog.pg_class c".  Note that "pg_namespace n" will be added.
+	 * Name of catalog or catalogs to be queried, with alias(es), eg.
+	 * "pg_catalog.pg_class c".  Note that "pg_namespace n" and/or
+	 * "pg_namespace ni" will be added automatically when needed.
 	 */
 	const char *catname;
 
@@ -140,12 +147,15 @@ typedef struct SchemaQuery
 	/*
 	 * Visibility condition --- which rows are visible without schema
 	 * qualification?  For example, "pg_catalog.pg_table_is_visible(c.oid)".
+	 * NULL if not needed.
 	 */
 	const char *viscondition;
 
 	/*
-	 * Namespace --- name of field to join to pg_namespace.oid. For example,
-	 * "c.relnamespace".
+	 * Namespace --- name of field to join to pg_namespace.oid when there is
+	 * schema qualification.  For example, "c.relnamespace".  NULL if we don't
+	 * want to join to pg_namespace (then any schema part in the input word
+	 * will be ignored).
 	 */
 	const char *namespace;
 
@@ -154,12 +164,43 @@ typedef struct SchemaQuery
 	 */
 	const char *result;
 
+	/*
+	 * In some cases, it's difficult to keep the query from returning the same
+	 * object multiple times.  Specify use_distinct to filter out duplicates.
+	 */
+	bool		use_distinct;
+
 	/*
 	 * Additional literal strings (usually keywords) to be offered along with
 	 * the query results.  Provide a NULL-terminated array of constant
 	 * strings, or NULL if none.
 	 */
 	const char *const *keywords;
+
+	/*
+	 * If this query uses completion_info_object/completion_info_schema,
+	 * populate the remaining fields, else leave them NULL.  When using this
+	 * capability, catname must include the catalog that defines the
+	 * completion_info_object, and selcondition must include the join
+	 * condition that connects it to the result's catalog.
+	 *
+	 * infoname is the field that should be equated to completion_info_object,
+	 * for example "ci.relname".
+	 */
+	const char *infoname;
+
+	/*
+	 * Visibility condition to use when completion_info_schema is not set. For
+	 * example, "pg_catalog.pg_table_is_visible(ci.oid)".  NULL if not needed.
+	 */
+	const char *infoviscondition;
+
+	/*
+	 * Name of field to join to pg_namespace.oid when completion_info_schema
+	 * is set.  For example, "ci.relnamespace".  NULL if we don't want to
+	 * consider completion_info_schema.
+	 */
+	const char *infonamespace;
 } SchemaQuery;
 
 
@@ -176,10 +217,10 @@ static int	completion_max_records;
 static char completion_last_char;	/* last char of input word */
 static const char *completion_charp;	/* to pass a string */
 static const char *const *completion_charpp;	/* to pass a list of strings */
-static const char *completion_info_charp;	/* to pass a second string */
-static const char *completion_info_charp2;	/* to pass a third string */
 static const VersionedQuery *completion_vquery; /* to pass a VersionedQuery */
 static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
+static char *completion_info_object;	/* name of a related object */
+static char *completion_info_schema;	/* schema name of a related object */
 static bool completion_case_sensitive;	/* completion is case sensitive */
 static bool completion_verbatim;	/* completion is verbatim */
 static bool completion_force_quote; /* true to force-quote filenames */
@@ -257,6 +298,14 @@ do { \
 	COMPLETE_WITH_SCHEMA_QUERY_LIST(query, list); \
 } while (0)
 
+#define COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(query) \
+do { \
+	completion_squery = &(query); \
+	completion_charpp = NULL; \
+	completion_verbatim = true; \
+	matches = rl_completion_matches(text, complete_from_schema_query); \
+} while (0)
+
 #define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query) \
 	COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, NULL)
 
@@ -312,29 +361,11 @@ do { \
 
 #define COMPLETE_WITH_ATTR_LIST(relation, list) \
 do { \
-	char   *_completion_schema; \
-	char   *_completion_table; \
-\
-	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								false, false, pset.encoding); \
-	if (_completion_table == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_attributes; \
-		completion_info_charp = relation; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema; \
-		completion_info_charp = _completion_table; \
-		completion_info_charp2 = _completion_schema; \
-	} \
+	setup_completion_info(relation); \
+	completion_squery = &(Query_for_list_of_attributes); \
 	completion_charpp = list; \
 	completion_verbatim = false; \
-	matches = rl_completion_matches(text, complete_from_query); \
+	matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)
 
 #define COMPLETE_WITH_ATTR_PLUS(relation, ...) \
@@ -351,65 +382,24 @@ do { \
  */
 #define COMPLETE_WITH_ENUM_VALUE(type) \
 do { \
-	char   *_completion_schema; \
-	char   *_completion_type; \
-	bool	use_quotes; \
-\
-	_completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-							   false, false, pset.encoding); \
-	use_quotes = (text[0] == '\'' || \
-				  start == 0 || rl_line_buffer[start - 1] != '\''); \
-	if (_completion_type == NULL) \
-	{ \
-		if (use_quotes) \
-			completion_charp = Query_for_list_of_enum_values_quoted; \
-		else \
-			completion_charp = Query_for_list_of_enum_values_unquoted; \
-		completion_info_charp = type; \
-	} \
+	setup_completion_info(type); \
+	if (text[0] == '\'' || \
+		start == 0 || rl_line_buffer[start - 1] != '\'') \
+		completion_squery = &(Query_for_list_of_enum_values_quoted); \
 	else \
-	{ \
-		if (use_quotes) \
-			completion_charp = Query_for_list_of_enum_values_with_schema_quoted; \
-		else \
-			completion_charp = Query_for_list_of_enum_values_with_schema_unquoted; \
-		completion_info_charp = _completion_type; \
-		completion_info_charp2 = _completion_schema; \
-	} \
+		completion_squery = &(Query_for_list_of_enum_values_unquoted); \
 	completion_charpp = NULL; \
 	completion_verbatim = true; \
-	matches = rl_completion_matches(text, complete_from_query); \
+	matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)
 
 #define COMPLETE_WITH_FUNCTION_ARG(function) \
 do { \
-	char   *_completion_schema; \
-	char   *_completion_function; \
-\
-	_completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-								 false, false, pset.encoding); \
-	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-				   false, false, pset.encoding); \
-	_completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-								   false, false, pset.encoding); \
-	if (_completion_function == NULL) \
-	{ \
-		completion_charp = Query_for_list_of_arguments; \
-		completion_info_charp = function; \
-	} \
-	else \
-	{ \
-		completion_charp = Query_for_list_of_arguments_with_schema; \
-		completion_info_charp = _completion_function; \
-		completion_info_charp2 = _completion_schema; \
-	} \
+	setup_completion_info(function); \
+	completion_squery = &(Query_for_list_of_arguments); \
 	completion_charpp = NULL; \
 	completion_verbatim = true; \
-	matches = rl_completion_matches(text, complete_from_query); \
+	matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)
 
 /*
@@ -419,6 +409,51 @@ do { \
  * unnecessary bloat in the completions generated.
  */
 
+static const SchemaQuery Query_for_constraint_of_table = {
+	.catname = "pg_catalog.pg_constraint con, pg_catalog.pg_class c1",
+	.selcondition = "con.conrelid=c1.oid",
+	.result = "con.conname",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_constraint_of_table_not_validated = {
+	.catname = "pg_catalog.pg_constraint con, pg_catalog.pg_class c1",
+	.selcondition = "con.conrelid=c1.oid and not con.convalidated",
+	.result = "con.conname",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_constraint_of_type = {
+	.catname = "pg_catalog.pg_constraint con, pg_catalog.pg_type t",
+	.selcondition = "con.contypid=t.oid",
+	.result = "con.conname",
+	.infoname = "t.typname",
+	.infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+	.infonamespace = "t.typnamespace",
+};
+
+static const SchemaQuery Query_for_index_of_table = {
+	.catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i",
+	.selcondition = "c1.oid=i.indrelid and i.indexrelid=c2.oid",
+	.result = "c2.relname",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_unique_index_of_table = {
+	.catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i",
+	.selcondition = "c1.oid=i.indrelid and i.indexrelid=c2.oid and i.indisunique",
+	.result = "c2.relname",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
 static const SchemaQuery Query_for_list_of_aggregates[] = {
 	{
 		.min_server_version = 110000,
@@ -437,6 +472,32 @@ static const SchemaQuery Query_for_list_of_aggregates[] = {
 	}
 };
 
+static const SchemaQuery Query_for_list_of_arguments = {
+	.catname = "pg_catalog.pg_proc p",
+	.result = "pg_catalog.oidvectortypes(p.proargtypes)||')'",
+	.infoname = "p.proname",
+	.infoviscondition = "pg_catalog.pg_function_is_visible(p.oid)",
+	.infonamespace = "p.pronamespace",
+};
+
+static const SchemaQuery Query_for_list_of_attributes = {
+	.catname = "pg_catalog.pg_attribute a, pg_catalog.pg_class c",
+	.selcondition = "c.oid = a.attrelid and a.attnum > 0 and not a.attisdropped",
+	.result = "a.attname",
+	.infoname = "c.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.infonamespace = "c.relnamespace",
+};
+
+static const SchemaQuery Query_for_list_of_attribute_numbers = {
+	.catname = "pg_catalog.pg_attribute a, pg_catalog.pg_class c",
+	.selcondition = "c.oid = a.attrelid and a.attnum > 0 and not a.attisdropped",
+	.result = "a.attnum::pg_catalog.text",
+	.infoname = "c.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.infonamespace = "c.relnamespace",
+};
+
 static const char *const Keywords_for_list_of_datatypes[] = {
 	"bigint",
 	"boolean",
@@ -497,6 +558,24 @@ static const SchemaQuery Query_for_list_of_domains = {
 	.result = "t.typname",
 };
 
+static const SchemaQuery Query_for_list_of_enum_values_quoted = {
+	.catname = "pg_catalog.pg_enum e, pg_catalog.pg_type t",
+	.selcondition = "t.oid = e.enumtypid",
+	.result = "pg_catalog.quote_literal(enumlabel)",
+	.infoname = "t.typname",
+	.infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+	.infonamespace = "t.typnamespace",
+};
+
+static const SchemaQuery Query_for_list_of_enum_values_unquoted = {
+	.catname = "pg_catalog.pg_enum e, pg_catalog.pg_type t",
+	.selcondition = "t.oid = e.enumtypid",
+	.result = "e.enumlabel",
+	.infoname = "t.typname",
+	.infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+	.infonamespace = "t.typnamespace",
+};
+
 /* Note: this intentionally accepts aggregates as well as plain functions */
 static const SchemaQuery Query_for_list_of_functions[] = {
 	{
@@ -571,6 +650,48 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = {
 	.result = "c.relname",
 };
 
+static const SchemaQuery Query_for_list_of_tables_for_constraint = {
+	.catname = "pg_catalog.pg_class c, pg_catalog.pg_constraint con",
+	.selcondition = "c.oid=con.conrelid and c.relkind IN ("
+	CppAsString2(RELKIND_RELATION) ", "
+	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.namespace = "c.relnamespace",
+	.result = "c.relname",
+	.use_distinct = true,
+	.infoname = "con.conname",
+};
+
+static const SchemaQuery Query_for_list_of_tables_for_policy = {
+	.catname = "pg_catalog.pg_class c, pg_catalog.pg_policy p",
+	.selcondition = "c.oid=p.polrelid",
+	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.namespace = "c.relnamespace",
+	.result = "c.relname",
+	.use_distinct = true,
+	.infoname = "p.polname",
+};
+
+static const SchemaQuery Query_for_list_of_tables_for_rule = {
+	.catname = "pg_catalog.pg_class c, pg_catalog.pg_rewrite r",
+	.selcondition = "c.oid=r.ev_class",
+	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.namespace = "c.relnamespace",
+	.result = "c.relname",
+	.use_distinct = true,
+	.infoname = "r.rulename",
+};
+
+static const SchemaQuery Query_for_list_of_tables_for_trigger = {
+	.catname = "pg_catalog.pg_class c, pg_catalog.pg_trigger t",
+	.selcondition = "c.oid=t.tgrelid",
+	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+	.namespace = "c.relnamespace",
+	.result = "c.relname",
+	.use_distinct = true,
+	.infoname = "t.tgname",
+};
+
 static const SchemaQuery Query_for_list_of_views = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")",
@@ -719,7 +840,6 @@ static const SchemaQuery Query_for_list_of_clusterables = {
 static const SchemaQuery Query_for_list_of_constraints_with_schema = {
 	.catname = "pg_catalog.pg_constraint c",
 	.selcondition = "c.conrelid <> 0",
-	.viscondition = "true",		/* there is no pg_constraint_is_visible */
 	.namespace = "c.connamespace",
 	.result = "c.conname",
 };
@@ -739,96 +859,56 @@ static const SchemaQuery Query_for_list_of_collations = {
 	.result = "c.collname",
 };
 
+static const SchemaQuery Query_for_partition_of_table = {
+	.catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i",
+	.selcondition = "c1.oid=i.inhparent and i.inhrelid=c2.oid and c2.relispartition",
+	.viscondition = "pg_catalog.pg_table_is_visible(c2.oid)",
+	.namespace = "c2.relnamespace",
+	.result = "c2.relname",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_rule_of_table = {
+	.catname = "pg_catalog.pg_rewrite r, pg_catalog.pg_class c1",
+	.selcondition = "r.ev_class=c1.oid",
+	.result = "r.rulename",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_trigger_of_table = {
+	.catname = "pg_catalog.pg_trigger t, pg_catalog.pg_class c1",
+	.selcondition = "t.tgrelid=c1.oid and not t.tgisinternal",
+	.result = "t.tgname",
+	.infoname = "c1.relname",
+	.infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+	.infonamespace = "c1.relnamespace",
+};
+
 
 /*
  * Queries to get lists of names of various kinds of things, possibly
- * restricted to names matching a partially entered name.  In these queries,
- * the first %s will be replaced by the text entered so far (suitably escaped
- * to become a SQL literal string).  %d will be replaced by the length of the
- * string (in unescaped form).  A second and third %s, if present, will be
- * replaced by a suitably-escaped version of the string provided in
- * completion_info_charp.  A fourth and fifth %s are similarly replaced by
- * completion_info_charp2.
+ * restricted to names matching a partially entered name.  Don't use
+ * this method where the user might wish to enter a schema-qualified
+ * name; make a SchemaQuery instead.
+ *
+ * In these queries, there must be a restriction clause of the form
+ *		substring(OUTPUT,1,%d)='%s'
+ * where "OUTPUT" is the same string that the query returns.  The %d and %s
+ * will be replaced by the string length of the text and the text itself,
+ * causing the results to be limited to those matching the already-typed text.
  *
- * Beware that the allowed sequences of %s and %d are determined by
- * _complete_from_query().
+ * There can be a second '%s', which will be replaced by a suitably-escaped
+ * version of the string provided in completion_info_object.  If there is a
+ * third '%s', it will be replaced by a suitably-escaped version of the string
+ * provided in completion_info_schema.  NOTE: using completion_info_object
+ * that way is usually the wrong thing, and using completion_info_schema
+ * that way is always the wrong thing.  Make a SchemaQuery instead.
  */
 
-#define Query_for_list_of_attributes \
-"SELECT attname "\
-"  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\
-" WHERE c.oid = a.attrelid "\
-"   AND a.attnum > 0 "\
-"   AND NOT a.attisdropped "\
-"   AND substring(attname,1,%d)='%s' "\
-"   AND (relname='%s' "\
-"        OR '\"' || relname || '\"'='%s') "\
-"   AND pg_catalog.pg_table_is_visible(c.oid)"
-
-#define Query_for_list_of_attribute_numbers \
-"SELECT attnum "\
-"  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\
-" WHERE c.oid = a.attrelid "\
-"   AND a.attnum > 0 "\
-"   AND NOT a.attisdropped "\
-"   AND substring(attnum::pg_catalog.text,1,%d)='%s' "\
-"   AND (relname='%s' "\
-"        OR '\"' || relname || '\"'='%s') "\
-"   AND pg_catalog.pg_table_is_visible(c.oid)"
-
-#define Query_for_list_of_attributes_with_schema \
-"SELECT attname "\
-"  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
-" WHERE c.oid = a.attrelid "\
-"   AND n.oid = c.relnamespace "\
-"   AND a.attnum > 0 "\
-"   AND NOT a.attisdropped "\
-"   AND substring(attname,1,%d)='%s' "\
-"   AND (relname='%s' "\
-"        OR '\"' || relname || '\"' ='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
-#define Query_for_list_of_enum_values_quoted \
-"SELECT pg_catalog.quote_literal(enumlabel) "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\
-" WHERE t.oid = e.enumtypid "\
-"   AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND pg_catalog.pg_type_is_visible(t.oid)"
-
-#define Query_for_list_of_enum_values_unquoted \
-"SELECT enumlabel "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\
-" WHERE t.oid = e.enumtypid "\
-"   AND substring(enumlabel,1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND pg_catalog.pg_type_is_visible(t.oid)"
-
-#define Query_for_list_of_enum_values_with_schema_quoted \
-"SELECT pg_catalog.quote_literal(enumlabel) "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t, pg_catalog.pg_namespace n "\
-" WHERE t.oid = e.enumtypid "\
-"   AND n.oid = t.typnamespace "\
-"   AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
-#define Query_for_list_of_enum_values_with_schema_unquoted \
-"SELECT enumlabel "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t, pg_catalog.pg_namespace n "\
-" WHERE t.oid = e.enumtypid "\
-"   AND n.oid = t.typnamespace "\
-"   AND substring(enumlabel,1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
 #define Query_for_list_of_template_databases \
 "SELECT d.datname "\
 "  FROM pg_catalog.pg_database d "\
@@ -887,81 +967,12 @@ static const SchemaQuery Query_for_list_of_collations = {
 #define Keywords_for_list_of_grant_roles \
 "PUBLIC", "CURRENT_ROLE", "CURRENT_USER", "SESSION_USER"
 
-#define Query_for_index_of_table \
-"SELECT c2.relname "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\
-" WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\
-"       and substring(c2.relname,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c2.oid)"
-
-#define Query_for_unique_index_of_table \
-Query_for_index_of_table \
-"       and i.indisunique"
-
-#define Query_for_constraint_of_table \
-"SELECT conname "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\
-" WHERE c1.oid=conrelid and substring(conname,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c1.oid)"
-
-#define Query_for_constraint_of_table_not_validated \
-Query_for_constraint_of_table \
-"       and not con.convalidated"
-
 #define Query_for_all_table_constraints \
 "SELECT conname "\
 "  FROM pg_catalog.pg_constraint c "\
 " WHERE c.conrelid <> 0 "\
 "       and substring(conname,1,%d)='%s'"
 
-#define Query_for_constraint_of_type \
-"SELECT conname "\
-"  FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\
-" WHERE t.oid=contypid and substring(conname,1,%d)='%s'"\
-"       and t.typname='%s'"\
-"       and pg_catalog.pg_type_is_visible(t.oid)"
-
-#define Query_for_list_of_tables_for_constraint \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT conrelid FROM pg_catalog.pg_constraint "\
-"         WHERE conname='%s')"
-
-#define Query_for_rule_of_table \
-"SELECT rulename "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_rewrite "\
-" WHERE c1.oid=ev_class and substring(rulename,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c1.oid)"
-
-#define Query_for_list_of_tables_for_rule \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT ev_class FROM pg_catalog.pg_rewrite "\
-"         WHERE rulename='%s')"
-
-#define Query_for_trigger_of_table \
-"SELECT tgname "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_trigger "\
-" WHERE c1.oid=tgrelid and substring(tgname,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c1.oid)"\
-"       and not tgisinternal"
-
-#define Query_for_list_of_tables_for_trigger \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT tgrelid FROM pg_catalog.pg_trigger "\
-"         WHERE tgname='%s')"
-
 #define Query_for_list_of_ts_configurations \
 "SELECT cfgname FROM pg_catalog.pg_ts_config "\
 " WHERE substring(cfgname,1,%d)='%s'"
@@ -1010,24 +1021,6 @@ Query_for_constraint_of_table \
 "  WHERE substring(amname,1,%d)='%s' AND "\
 "   amtype=" CppAsString2(AMTYPE_TABLE)
 
-#define Query_for_list_of_arguments \
-"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
-"  FROM pg_catalog.pg_proc "\
-" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\
-"   AND (proname='%s'"\
-"        OR '\"' || proname || '\"'='%s') "\
-"   AND (pg_catalog.pg_function_is_visible(pg_proc.oid))"
-
-#define Query_for_list_of_arguments_with_schema \
-"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
-"  FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
-" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\
-"   AND n.oid = p.pronamespace "\
-"   AND (proname='%s' "\
-"        OR '\"' || proname || '\"' ='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
 #define Query_for_list_of_extensions \
 " SELECT extname "\
 "   FROM pg_catalog.pg_extension "\
@@ -1074,30 +1067,18 @@ Query_for_constraint_of_table \
 "   FROM pg_catalog.pg_policy "\
 "  WHERE substring(polname,1,%d)='%s'"
 
-#define Query_for_list_of_tables_for_policy \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT polrelid FROM pg_catalog.pg_policy "\
-"         WHERE polname='%s')"
-
-#define Query_for_enum \
-" SELECT name FROM ( "\
-"   SELECT pg_catalog.unnest(enumvals) AS name "\
+#define Query_for_values_of_enum_GUC \
+" SELECT val FROM ( "\
+"   SELECT name, pg_catalog.unnest(enumvals) AS val "\
 "     FROM pg_catalog.pg_settings "\
-"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\
 "    ) ss "\
-"  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
+"  WHERE substring(val,1,%d)='%s'"\
+"        and pg_catalog.lower(name)=pg_catalog.lower('%s')"
 
-#define Query_for_partition_of_table \
-"SELECT c2.relname "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i"\
-" WHERE c1.oid=i.inhparent and i.inhrelid=c2.oid"\
-"       and substring(c2.relname,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c2.oid)"\
-"       and c2.relispartition = 'true'"
+#define Query_for_list_of_channels \
+" SELECT channel "\
+"   FROM pg_catalog.pg_listening_channels() AS channel "\
+"  WHERE substring(channel,1,%d)='%s'"
 
 #define Query_for_list_of_cursors \
 " SELECT name "\
@@ -1280,6 +1261,7 @@ static char *_complete_from_query(const char *simple_query,
 								  const char *const *keywords,
 								  bool verbatim,
 								  const char *text, int state);
+static void setup_completion_info(const char *word);
 static char *complete_from_list(const char *text, int state);
 static char *complete_from_const(const char *text, int state);
 static void append_variable_names(char ***varnames, int *nvars,
@@ -1685,8 +1667,10 @@ psql_completion(const char *text, int start, int end)
 	/* Clear a few things. */
 	completion_charp = NULL;
 	completion_charpp = NULL;
-	completion_info_charp = NULL;
-	completion_info_charp2 = NULL;
+	completion_vquery = NULL;
+	completion_squery = NULL;
+	completion_info_object = NULL;
+	completion_info_schema = NULL;
 
 	/*
 	 * Scan the input line to extract the words before our current position.
@@ -1866,14 +1850,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER EXTENSION <name> UPDATE */
 	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
-		completion_info_charp = prev2_wd;
+		setup_completion_info(prev2_wd);
 		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
 	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
-		completion_info_charp = prev3_wd;
+		setup_completion_info(prev3_wd);
 		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions);
 	}
 
@@ -1913,8 +1897,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER INDEX <name> ALTER COLUMN */
 	else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_attribute_numbers);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(Query_for_list_of_attribute_numbers);
 	}
 	/* ALTER INDEX <name> ALTER COLUMN <colnum> */
 	else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
@@ -2024,8 +2008,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_type);
 	}
 	/* ALTER DOMAIN <sth> RENAME */
 	else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -2110,8 +2094,8 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER POLICY <name> ON <table> */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	/* ALTER POLICY <name> ON <table> - show options */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny))
@@ -2134,8 +2118,8 @@ psql_completion(const char *text, int start, int end)
 	/* If we have ALTER RULE <name> ON, then add the correct tablename */
 	else if (Matches("ALTER", "RULE", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule);
 	}
 
 	/* ALTER RULE <name> ON <name> */
@@ -2150,18 +2134,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "TRIGGER", MatchAny))
 		COMPLETE_WITH("ON");
 
-	else if (Matches("ALTER", "TRIGGER", MatchAny, MatchAny))
+	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger);
 	}
 
-	/*
-	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
-	 */
-	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
-
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("RENAME TO", "DEPENDS ON EXTENSION",
@@ -2200,28 +2178,28 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE xxx ADD PRIMARY KEY USING INDEX */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "PRIMARY", "KEY", "USING", "INDEX"))
 	{
-		completion_info_charp = prev6_wd;
-		COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+		setup_completion_info(prev6_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
 	}
 	/* ALTER TABLE xxx ADD UNIQUE USING INDEX */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "UNIQUE", "USING", "INDEX"))
 	{
-		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+		setup_completion_info(prev5_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
 	}
 	/* ALTER TABLE xxx ADD CONSTRAINT yyy PRIMARY KEY USING INDEX */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny,
 					 "PRIMARY", "KEY", "USING", "INDEX"))
 	{
-		completion_info_charp = prev8_wd;
-		COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+		setup_completion_info(prev8_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
 	}
 	/* ALTER TABLE xxx ADD CONSTRAINT yyy UNIQUE USING INDEX */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny,
 					 "UNIQUE", "USING", "INDEX"))
 	{
-		completion_info_charp = prev7_wd;
-		COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+		setup_completion_info(prev7_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
 	}
 	/* ALTER TABLE xxx ENABLE */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE"))
@@ -2231,23 +2209,23 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("RULE", "TRIGGER");
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
 	{
-		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		setup_completion_info(prev4_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
 	{
-		completion_info_charp = prev4_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		setup_completion_info(prev4_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table);
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -2263,13 +2241,13 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER");
 	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table);
 	}
 
 	/* ALTER TABLE xxx ALTER */
@@ -2299,14 +2277,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE <sth> ALTER|DROP|RENAME CONSTRAINT <constraint> */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME", "CONSTRAINT"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table);
 	}
 	/* ALTER TABLE <sth> VALIDATE CONSTRAINT <non-validated constraint> */
 	else if (Matches("ALTER", "TABLE", MatchAny, "VALIDATE", "CONSTRAINT"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_constraint_of_table_not_validated);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table_not_validated);
 	}
 	/* ALTER TABLE ALTER [COLUMN] <foo> */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -2338,8 +2316,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
 	}
 	/* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
 	else if (Matches("ALTER", "TABLE", MatchAny, "SET"))
@@ -2370,8 +2348,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_LIST(table_storage_parameters);
 	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
-		completion_info_charp = prev5_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		setup_completion_info(prev5_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
 		COMPLETE_WITH("INDEX");
@@ -2398,8 +2376,8 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
 	{
-		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_partition_of_table);
+		setup_completion_info(prev3_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_partition_of_table);
 	}
 	else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION", MatchAny))
 		COMPLETE_WITH("CONCURRENTLY", "FINALIZE");
@@ -2536,8 +2514,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("CLUSTER", MatchAny, "USING") ||
 			 Matches("CLUSTER", "VERBOSE|(*)", MatchAny, "USING"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_index_of_table);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
 	}
 	else if (HeadMatches("CLUSTER", "(*") &&
 			 !HeadMatches("CLUSTER", "(*)"))
@@ -2574,9 +2552,9 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_tables_for_constraint,
-								 "DOMAIN");
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables_for_constraint,
+										"DOMAIN");
 	}
 	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON", "DOMAIN"))
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
@@ -2594,8 +2572,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON", "POLICY", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	else if (Matches("COMMENT", "ON", "PROCEDURAL", "LANGUAGE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
@@ -2603,8 +2581,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON", "RULE", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule);
 	}
 	else if (Matches("COMMENT", "ON", "TEXT", "SEARCH"))
 		COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
@@ -2622,15 +2600,15 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("LANGUAGE");
 	else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
 	{
-		completion_info_charp = prev2_wd;
+		setup_completion_info(prev2_wd);
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	}
 	else if (Matches("COMMENT", "ON", "TRIGGER", MatchAny))
 		COMPLETE_WITH("ON");
 	else if (Matches("COMMENT", "ON", "TRIGGER", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger);
 	}
 	else if (Matches("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
 			 Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
@@ -2746,7 +2724,7 @@ psql_completion(const char *text, int start, int end)
 	/* CREATE EXTENSION <name> VERSION */
 	else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
-		completion_info_charp = prev2_wd;
+		setup_completion_info(prev2_wd);
 		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions);
 	}
 
@@ -3057,7 +3035,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("CREATE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE") ||
 			 Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
 	{
-		completion_info_charp = prev2_wd;
+		setup_completion_info(prev2_wd);
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	}
 
@@ -3480,8 +3458,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (Matches("DROP", "TRIGGER", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger);
 	}
 	else if (Matches("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
@@ -3507,8 +3485,8 @@ psql_completion(const char *text, int start, int end)
 	/* DROP POLICY <name> ON <table> */
 	else if (Matches("DROP", "POLICY", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy);
 	}
 	else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
@@ -3518,8 +3496,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (Matches("DROP", "RULE", MatchAny, "ON"))
 	{
-		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+		setup_completion_info(prev2_wd);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule);
 	}
 	else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
@@ -3533,7 +3511,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("LANGUAGE");
 	else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
 	{
-		completion_info_charp = prev2_wd;
+		setup_completion_info(prev2_wd);
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	}
 	else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny))
@@ -3917,7 +3895,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	else if (TailMatches("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(channel,1,%d)='%s'");
+		COMPLETE_WITH_QUERY(Query_for_list_of_channels);
 
 /* OPTIONS */
 	else if (TailMatches("OPTIONS"))
@@ -4153,11 +4131,9 @@ psql_completion(const char *text, int start, int end)
 			{
 				if (strcmp(guctype, "enum") == 0)
 				{
-					char		querybuf[1024];
-
-					snprintf(querybuf, sizeof(querybuf),
-							 Query_for_enum, prev2_wd);
-					COMPLETE_WITH_QUERY_PLUS(querybuf, "DEFAULT");
+					setup_completion_info(prev2_wd);
+					COMPLETE_WITH_QUERY_PLUS(Query_for_values_of_enum_GUC,
+											 "DEFAULT");
 				}
 				else if (strcmp(guctype, "bool") == 0)
 					COMPLETE_WITH("on", "off", "true", "false", "yes", "no",
@@ -4202,9 +4178,7 @@ psql_completion(const char *text, int start, int end)
 
 /* UNLISTEN */
 	else if (Matches("UNLISTEN"))
-		COMPLETE_WITH_QUERY_PLUS("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel"
-								 " WHERE substring(channel,1,%d)='%s'",
-								 "*");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_channels, "*");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
@@ -4405,7 +4379,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 
 	else if (TailMatchesCS("\\encoding"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_encodings);
 	else if (TailMatchesCS("\\h|\\help"))
 		COMPLETE_WITH_LIST(sql_commands);
 	else if (TailMatchesCS("\\h|\\help", MatchAny))
@@ -4564,6 +4538,12 @@ psql_completion(const char *text, int start, int end)
 	free(previous_words);
 	free(words_buffer);
 	free(text_copy);
+	if (completion_info_object)
+		free(completion_info_object);
+	completion_info_object = NULL;
+	if (completion_info_schema)
+		free(completion_info_schema);
+	completion_info_schema = NULL;
 
 	/* Return our Grand List O' Matches */
 	return matches;
@@ -4707,10 +4687,11 @@ complete_from_versioned_schema_query(const char *text, int state)
  * where "OUTPUT" is the same string that the query returns.  The %d and %s
  * will be replaced by the string length of the text and the text itself,
  * causing the results to be limited to those matching the already-typed text.
- * The query may also have up to four more %s in it; the first two such will
- * be replaced by the value of completion_info_charp, the next two by the
- * value of completion_info_charp2.  (These strings will be escaped to
- * become SQL literals, so what is actually in the query should be '%s'.)
+ * There can be a second '%s', which will be replaced by a suitably-escaped
+ * version of the string provided in completion_info_object.  If there is a
+ * third '%s', it will be replaced by a suitably-escaped version of the string
+ * provided in completion_info_schema.  Those strings should be set up
+ * by calling setup_completion_info().
  * Simple queries should return a single column of matches.  If "verbatim"
  * is true, the matches are returned as-is; otherwise, they are taken to
  * be SQL identifiers and quoted if necessary.
@@ -4763,8 +4744,8 @@ _complete_from_query(const char *simple_query,
 		int			object_length = 0;
 		char	   *e_schemaname;
 		char	   *e_objectname;
-		char	   *e_info_charp;
-		char	   *e_info_charp2;
+		char	   *e_info_object;
+		char	   *e_info_schema;
 
 		/* Reset static state, ensuring no memory leaks */
 		list_index = 0;
@@ -4806,15 +4787,15 @@ _complete_from_query(const char *simple_query,
 
 		e_objectname = escape_string(objectname);
 
-		if (completion_info_charp)
-			e_info_charp = escape_string(completion_info_charp);
+		if (completion_info_object)
+			e_info_object = escape_string(completion_info_object);
 		else
-			e_info_charp = NULL;
+			e_info_object = NULL;
 
-		if (completion_info_charp2)
-			e_info_charp2 = escape_string(completion_info_charp2);
+		if (completion_info_schema)
+			e_info_schema = escape_string(completion_info_schema);
 		else
-			e_info_charp2 = NULL;
+			e_info_schema = NULL;
 
 		initPQExpBuffer(&query_buffer);
 
@@ -4827,20 +4808,44 @@ _complete_from_query(const char *simple_query,
 			 * already qualified or not.  schema_query gives us the pieces to
 			 * assemble.
 			 */
-			if (schemaname == NULL)
+			if (schemaname == NULL || schema_query->namespace == NULL)
 			{
 			/* Get unqualified names matching the input-so-far */
-			appendPQExpBuffer(&query_buffer, "SELECT %s, NULL::pg_catalog.text FROM %s WHERE ",
+			appendPQExpBufferStr(&query_buffer, "SELECT ");
+			if (schema_query->use_distinct)
+				appendPQExpBufferStr(&query_buffer, "DISTINCT ");
+			appendPQExpBuffer(&query_buffer,
+							  "%s, NULL::pg_catalog.text FROM %s",
 							  schema_query->result,
 							  schema_query->catname);
+			if (schema_query->infonamespace && completion_info_schema)
+				appendPQExpBufferStr(&query_buffer,
+									 ", pg_catalog.pg_namespace ni");
+			appendPQExpBufferStr(&query_buffer, " WHERE ");
 			if (schema_query->selcondition)
 				appendPQExpBuffer(&query_buffer, "%s AND ",
 								  schema_query->selcondition);
 			appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
 							  schema_query->result,
 							  object_length, e_objectname);
+			if (schema_query->viscondition)
 			appendPQExpBuffer(&query_buffer, " AND %s",
 							  schema_query->viscondition);
+			if (schema_query->infoname)
+			{
+				Assert(completion_info_object);
+				appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
+								  schema_query->infoname, e_info_object);
+				if (schema_query->infonamespace && completion_info_schema)
+					appendPQExpBuffer(&query_buffer,
+									  " AND %s = ni.oid AND ni.nspname = '%s'",
+									  schema_query->infonamespace,
+									  e_info_schema);
+				else if (schema_query->infoviscondition)
+					appendPQExpBuffer(&query_buffer,
+									  " AND %s",
+									  schema_query->infoviscondition);
+			}
 
 			/*
 			 * When fetching relation names, suppress system catalogs unless
@@ -4857,7 +4862,12 @@ _complete_from_query(const char *simple_query,
 									 " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
 			}
 
-			/* Add in schema names matching the input-so-far */
+			/*
+			 * If the target object type can be schema-qualified, add in
+			 * schema names matching the input-so-far.
+			 */
+			if (schema_query->namespace)
+			{
 			appendPQExpBuffer(&query_buffer, "\nUNION ALL\n"
 							  "SELECT NULL::pg_catalog.text, n.nspname "
 							  "FROM pg_catalog.pg_namespace n "
@@ -4878,14 +4888,21 @@ _complete_from_query(const char *simple_query,
 			 */
 			schemaquoted = objectquoted;
 			}
+			}
 			else
 			{
 			/* Input is qualified, so produce only qualified names */
-			appendPQExpBuffer(&query_buffer, "SELECT %s, n.nspname "
-							  "FROM %s, pg_catalog.pg_namespace n "
-							  "WHERE %s = n.oid AND ",
+			appendPQExpBufferStr(&query_buffer, "SELECT ");
+			if (schema_query->use_distinct)
+				appendPQExpBufferStr(&query_buffer, "DISTINCT ");
+			appendPQExpBuffer(&query_buffer, "%s, n.nspname "
+							  "FROM %s, pg_catalog.pg_namespace n",
 							  schema_query->result,
-							  schema_query->catname,
+							  schema_query->catname);
+			if (schema_query->infonamespace && completion_info_schema)
+				appendPQExpBufferStr(&query_buffer,
+									 ", pg_catalog.pg_namespace ni");
+			appendPQExpBuffer(&query_buffer, " WHERE %s = n.oid AND ",
 							  schema_query->namespace);
 			if (schema_query->selcondition)
 				appendPQExpBuffer(&query_buffer, "%s AND ",
@@ -4895,6 +4912,21 @@ _complete_from_query(const char *simple_query,
 							  object_length, e_objectname);
 			appendPQExpBuffer(&query_buffer, "n.nspname = '%s'",
 							  e_schemaname);
+			if (schema_query->infoname)
+			{
+				Assert(completion_info_object);
+				appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
+								  schema_query->infoname, e_info_object);
+				if (schema_query->infonamespace && completion_info_schema)
+					appendPQExpBuffer(&query_buffer,
+									  " AND %s = ni.oid AND ni.nspname = '%s'",
+									  schema_query->infonamespace,
+									  e_info_schema);
+				else if (schema_query->infoviscondition)
+					appendPQExpBuffer(&query_buffer,
+									  " AND %s",
+									  schema_query->infoviscondition);
+			}
 			}
 		}
 		else
@@ -4903,8 +4935,7 @@ _complete_from_query(const char *simple_query,
 			/* simple_query is an sprintf-style format string */
 			appendPQExpBuffer(&query_buffer, simple_query,
 							  object_length, e_objectname,
-							  e_info_charp, e_info_charp,
-							  e_info_charp2, e_info_charp2);
+							  e_info_object, e_info_schema);
 		}
 
 		/* Limit the number of records in the result */
@@ -4919,10 +4950,10 @@ _complete_from_query(const char *simple_query,
 		if (e_schemaname)
 			free(e_schemaname);
 		free(e_objectname);
-		if (e_info_charp)
-			free(e_info_charp);
-		if (e_info_charp2)
-			free(e_info_charp2);
+		if (e_info_object)
+			free(e_info_object);
+		if (e_info_schema)
+			free(e_info_schema);
 	}
 
 	/* Return the next result, if any, but not if the query failed */
@@ -5031,6 +5062,23 @@ _complete_from_query(const char *simple_query,
 }
 
 
+/*
+ * Set up completion_info_object and completion_info_schema
+ * by parsing the given word.  These variables can then be
+ * used in a query passed to _complete_from_query.
+ */
+static void
+setup_completion_info(const char *word)
+{
+	bool		schemaquoted,
+				objectquoted;
+
+	parse_identifier(word,
+					 &completion_info_schema, &completion_info_object,
+					 &schemaquoted, &objectquoted);
+}
+
+
 /*
  * This function returns in order one of a fixed, NULL pointer terminated list
  * of strings (if matching). This can be used if there are only a fixed number
