I'm regularly surprised that the only non-static function we seem to
have for getting a relation name by oid is get_rel_name. It doesn't
handle schema qualification at all, and it returns NULL rather than
ERROR'ing.

Doing it correctly involves interacting with the syscache, calling
RelationIsVisible and calling get_namespace_name if needed, then
passing the result to quote_qualified_identifier.

so here's what I propose to do:

Add get_qualified_relation_name(bool force_qualified, bool missing_ok)
to ruleutils.c and builtins.h.

(Unless there's somewhere better? I wanted to put it in lsyscache.c
alongside get_rel_name, but it uses quote_qualified_identifier,
StringInfo, etc, so it doesn't fit well in lsyscache.c.)

Replace generate_qualified_relation_name in ruleutils.c with a call to
get_qualified_relation_name(relid, true, false) .

Add comments to RelationGetRelationName, get_rel_name and regclassout
pointing to get_qualified_relation_name.

The only part I don't like here is the two boolean arguments, since
they're ugly to read. But inventing a new flags field seemed a bit
heavy for such a trivial task.

I had a quick look through the codebase for places where this pattern
is repeated and found astonishingly few. In most places we just use
get_rel_name and hope the user can figure out any ambiguity.

I did notice that in heap_copy_data and AlterTableMoveAll we manually
schema-qualify a relation name and fail to call
quote_qualified_identifier, so it's unclear if we mean a relation
named "my.relation" or the relation "relation" in schema "my". But in
diagnostic output, meh, whatever.

Arguably regclassout does the same thing as
get_qualified_relation_name, but I didn't replace it because it cares
about IsBootstrapProcessingMode().

(Prompted by https://dba.stackexchange.com/q/187788/7788)

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services
From 011cd6e08d6d29854db637d6beb8709615f376cb Mon Sep 17 00:00:00 2001
From: Craig Ringer <cr...@2ndquadrant.com>
Date: Fri, 6 Oct 2017 10:48:17 +0800
Subject: [PATCH v1] Add get_qualified_relation_name

It was surprisingly hard to just get a table name in PostgreSQL at the
C level.

To simplify that, expose the functionality of generate_qualified_relation_name
and generate_relation_name to callers in the wider codebase and to extensions
in the form of a new get_qualified_relation_name function.
---
 src/backend/utils/adt/regproc.c     |  2 +
 src/backend/utils/adt/ruleutils.c   | 81 +++++++++++++++++++++++++++----------
 src/backend/utils/cache/lsyscache.c |  5 ++-
 src/include/utils/builtins.h        |  2 +
 src/include/utils/rel.h             |  4 ++
 5 files changed, 70 insertions(+), 24 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 6fe81fa..691efa8 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -967,6 +967,8 @@ to_regclass(PG_FUNCTION_ARGS)
 
 /*
  * regclassout		- converts class OID to "class_name"
+ *
+ * (See get_qualified_relation_name for a direct-callable version).
  */
 Datum
 regclassout(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index eb01f35..0eccc02 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10483,6 +10483,9 @@ quote_qualified_identifier(const char *qualifier,
  *
  * This differs from the underlying get_rel_name() function in that it will
  * throw error instead of silently returning NULL if the OID is bad.
+ *
+ * The returned relation name is not guaranteed to be unique outside the
+ * schema, so it is frequently preferable to use get_qualified_relation_name.
  */
 static char *
 get_relation_name(Oid relid)
@@ -10495,6 +10498,56 @@ get_relation_name(Oid relid)
 }
 
 /*
+ * get_qualified_relation_name
+ *
+ * Get a relation name as a palloc'd string in the current memory context.
+ *
+ * If the relation is not on the current search path, or if force_qualify is
+ * true, the namespace for the relation is prepended.
+ *
+ * ERROR's on a missing relation unless missing_ok is set, in which case returns
+ * NULL.
+ *
+ * The quoting rules used are the same as those for regclass quoting: each
+ * component is quoted if necessary to prevent ambiguity.
+ */
+char *
+get_qualified_relation_name(Oid relid, bool force_qualify, bool missing_ok)
+{
+	HeapTuple   tp;
+	Form_pg_class reltup;
+	char       *relname;
+	char       *nspname = NULL;
+	char       *result;
+
+	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
+		else
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
+
+	reltup = (Form_pg_class) GETSTRUCT(tp);
+	relname = NameStr(reltup->relname);
+
+	if (force_qualify || !RelationIsVisible(relid))
+	{
+		nspname = get_namespace_name(reltup->relnamespace);
+		if (!nspname)
+			elog(ERROR, "cache lookup failed for namespace %u",
+					reltup->relnamespace);
+	}
+
+	result = quote_qualified_identifier(nspname, relname);
+
+	ReleaseSysCache(tp);
+
+	return result;
+}
+
+/*
  * generate_relation_name
  *		Compute the name to display for a relation specified by OID
  *
@@ -10503,6 +10556,11 @@ get_relation_name(Oid relid)
  * If namespaces isn't NIL, it must be a list of deparse_namespace nodes.
  * We will forcibly qualify the relation name if it equals any CTE name
  * visible in the namespace list.
+ *
+ * This doesn't use get_qualified_relation_name because it has
+ * to do CTE namespace clash checks to decide whether to force-qualify
+ * the name. For that it needs the namespace, and we don't want to do
+ * a second syscache lookup by calling get_qualified_relation_name.
  */
 static char *
 generate_relation_name(Oid relid, List *namespaces)
@@ -10567,28 +10625,7 @@ generate_relation_name(Oid relid, List *namespaces)
 static char *
 generate_qualified_relation_name(Oid relid)
 {
-	HeapTuple	tp;
-	Form_pg_class reltup;
-	char	   *relname;
-	char	   *nspname;
-	char	   *result;
-
-	tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
-	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
-	reltup = (Form_pg_class) GETSTRUCT(tp);
-	relname = NameStr(reltup->relname);
-
-	nspname = get_namespace_name(reltup->relnamespace);
-	if (!nspname)
-		elog(ERROR, "cache lookup failed for namespace %u",
-			 reltup->relnamespace);
-
-	result = quote_qualified_identifier(nspname, relname);
-
-	ReleaseSysCache(tp);
-
-	return result;
+	return get_qualified_relation_name(relid, true, false);
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 82763f8..0c8fdfc 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1719,8 +1719,9 @@ get_relnatts(Oid relid)
  *
  * Returns a palloc'd copy of the string, or NULL if no such relation.
  *
- * NOTE: since relation name is not unique, be wary of code that uses this
- * for anything except preparing error messages.
+ * NOTE: since relation name is not unique, be wary of code that uses this for
+ * anything except preparing error messages. See get_qualified_relation_name
+ * for a safer option.
  */
 char *
 get_rel_name(Oid relid)
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 762532f..105daef 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -78,6 +78,8 @@ extern bool quote_all_identifiers;
 extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 						   const char *ident);
+extern char *get_qualified_relation_name(Oid relid, bool force_qualify,
+										 bool missing_ok);
 
 /* varchar.c */
 extern int	bpchartruelen(char *s, int len);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4bc61e5..f68142c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -432,6 +432,8 @@ typedef struct ViewOptions
  *		Returns the rel's name.
  *
  * Note that the name is only unique within the containing namespace.
+ *
+ * See also get_rel_name(...), and get_qualified_relation_name(...).
  */
 #define RelationGetRelationName(relation) \
 	(NameStr((relation)->rd_rel->relname))
@@ -439,6 +441,8 @@ typedef struct ViewOptions
 /*
  * RelationGetNamespace
  *		Returns the rel's namespace OID.
+ *
+ * See also get_namespace_name(...).
  */
 #define RelationGetNamespace(relation) \
 	((relation)->rd_rel->relnamespace)
-- 
2.9.5

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to