>From 4d5f038a39b88b616f332df3ac52a22469f7203a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 22 Dec 2014 14:50:52 -0300
Subject: [PATCH 4/5] Add sql-callable pg_get_object_address

---
 src/backend/catalog/objectaddress.c    | 180 +++++++++++++++++++++++++++++++++
 src/include/catalog/pg_proc.h          |   3 +
 src/include/utils/builtins.h           |   3 +
 src/test/regress/sql/alter_generic.sql |  68 +++++++++++++
 4 files changed, 254 insertions(+)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 59431a9..74dca73 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1368,6 +1368,186 @@ get_object_address_opcf(ObjectType objtype,
 }
 
 /*
+ * Convert an array of TEXT into a List of string Values, as emitted by the
+ * parser, which is what get_object_address uses as input.
+ */
+static List *
+textarray_to_strvaluelist(ArrayType *arr)
+{
+	Datum  *elems;
+	bool   *nulls;
+	int		nelems;
+	List   *list = NIL;
+	int		i;
+
+	deconstruct_array(arr, TEXTOID, -1, false, 'i',
+					  &elems, &nulls, &nelems);
+
+	for (i = 0; i < nelems; i++)
+	{
+		Assert(nulls[i] == false);
+		list = lappend(list, makeString(TextDatumGetCString(elems[i])));
+	}
+
+	return list;
+}
+
+/*
+ * SQL-callable version of get_object_address
+ *
+ * Note: this function is not strict to allow objargs to be NULL, which is the
+ * most common case.  Nulls in the other arguments cause an error to be raised.
+ */
+Datum
+pg_get_object_address(PG_FUNCTION_ARGS)
+{
+	char   *ttype;
+	ObjectType type;
+	ArrayType *namearr;
+	List   *name;
+	List   *args;
+	ObjectAddress addr;
+	TupleDesc tupdesc;
+	Datum	values[3];
+	bool	nulls[3];
+	HeapTuple htup;
+	Relation	relation;
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errmsg("object type must not be null")));
+	if (PG_ARGISNULL(1))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("object name array must not be null")));
+
+	/* Decode object type, raise error if unknown */
+	ttype = TextDatumGetCString(PG_GETARG_TEXT_P(0));
+	type = unstringify_objtype(ttype);
+	if (type < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized object type \"%s\"", ttype)));
+
+	/*
+	 * For some object types, the "name" array must be passed as a TypeName;
+	 * for all other cases, use a list of a string Values.
+	 */
+	namearr = PG_GETARG_ARRAYTYPE_P(1);
+	if (type == OBJECT_TYPE || type == OBJECT_DOMAIN)
+	{
+		Datum	*elems;
+		bool	*nulls;
+		int		nelems;
+		TypeName *type;
+
+		deconstruct_array(namearr, TEXTOID, -1, false, 'i',
+						  &elems, &nulls, &nelems);
+		if (nelems != 1)
+			ereport(ERROR,
+					(errmsg("must supply only one type name")));
+		if (nulls[0])
+			ereport(ERROR,
+					(errmsg("type name must not be null")));
+		type = typeStringToTypeName(TextDatumGetCString(elems[0]));
+		name = type->names;
+	}
+	else if (type == OBJECT_CAST)
+	{
+		Datum	*elems;
+		bool	*nulls;
+		int		nelems;
+
+		deconstruct_array(namearr, TEXTOID, -1, false, 'i',
+						  &elems, &nulls, &nelems);
+		if (nelems != 1)
+			ereport(ERROR,
+					(errmsg("must supply only one type name")));
+		if (nulls[0])
+			ereport(ERROR,
+					(errmsg("type name must not be null")));
+		name = list_make1(typeStringToTypeName(TextDatumGetCString(elems[0])));
+	}
+	else
+		name = textarray_to_strvaluelist(namearr);
+
+	/*
+	 * If args are given, decode them according to the object type.
+	 */
+	if (!PG_ARGISNULL(2))
+	{
+		ArrayType *arrargs = PG_GETARG_ARRAYTYPE_P(2);
+
+		if (type == OBJECT_AGGREGATE ||
+			type == OBJECT_FUNCTION ||
+			type == OBJECT_OPERATOR ||
+			type == OBJECT_CAST)
+		{
+			/* in these cases, the args list must be of TypeName */
+			Datum  *elems;
+			bool   *nulls;
+			int		nelems;
+			int		i;
+
+			deconstruct_array(arrargs, TEXTOID, -1, false, 'i',
+							  &elems, &nulls, &nelems);
+
+			args = NIL;
+			for (i = 0; i < nelems; i++)
+			{
+				Assert(nulls[i] == false);
+				args = lappend(args,
+							   typeStringToTypeName(TextDatumGetCString(elems[i])));
+			}
+		}
+		else
+		{
+			/* For all other object types, use string Values */
+			args = textarray_to_strvaluelist(arrargs);
+		}
+	}
+	else
+	{
+		/* args is NULL */
+		args = NIL;
+	}
+
+	if (type == OBJECT_OPCLASS ||
+		type == OBJECT_OPFAMILY)
+	{
+		if (list_length(args) != 1)
+			ereport(ERROR,
+				   (errmsg("length(args) must equal 1")));
+	}
+
+	addr = get_object_address(type, name, args,
+							  &relation, AccessShareLock, false);
+
+	if (relation)
+		relation_close(relation, AccessShareLock);
+
+	tupdesc = CreateTemplateTupleDesc(3, false);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "classid",
+					   OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "objid",
+					   OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "objsubid",
+					   INT4OID, -1, 0);
+	tupdesc = BlessTupleDesc(tupdesc);
+
+	values[0] = ObjectIdGetDatum(addr.classId);
+	values[1] = ObjectIdGetDatum(addr.objectId);
+	values[2] = Int32GetDatum(addr.objectSubId);
+	nulls[0] = false;
+	nulls[1] = false;
+	nulls[2] = false;
+
+	htup = heap_form_tuple(tupdesc, values, nulls);
+
+	PG_RETURN_DATUM(HeapTupleGetDatum(htup));
+}
+
+/*
  * Check ownership of an object previously identified by get_object_address.
  */
 void
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index f766ed7..932969a 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3036,6 +3036,9 @@ DESCR("get identification of SQL object");
 DATA(insert OID = 3839 (  pg_identify_object		PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 2249 "26 26 23" "{26,26,23,25,25,25,25}" "{i,i,i,o,o,o,o}" "{classid,objid,subobjid,type,schema,name,identity}" _null_ pg_identify_object _null_ _null_ _null_ ));
 DESCR("get machine-parseable identification of SQL object");
 
+DATA(insert OID = 3954 (  pg_get_object_address    PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2249 "25 1009 1009" "{25,1009,1009,26,26,23}" "{i,i,i,o,o,o}" "{type,name,args,classid,objid,subobjid}" _null_ pg_get_object_address _null_ _null_ _null_ ));
+DESCR("get OID-based object address from name/args arrays");
+
 DATA(insert OID = 2079 (  pg_table_is_visible		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_table_is_visible _null_ _null_ _null_ ));
 DESCR("is table visible in search path?");
 DATA(insert OID = 2080 (  pg_type_is_visible		PGNSP PGUID 12 10 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_ pg_type_is_visible _null_ _null_ _null_ ));
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2da3002..7c4d291 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1195,6 +1195,9 @@ extern Datum pg_last_committed_xact(PG_FUNCTION_ARGS);
 extern Datum pg_describe_object(PG_FUNCTION_ARGS);
 extern Datum pg_identify_object(PG_FUNCTION_ARGS);
 
+/* catalog/objectaddress.c */
+extern Datum pg_get_object_address(PG_FUNCTION_ARGS);
+
 /* commands/constraint.c */
 extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
 
diff --git a/src/test/regress/sql/alter_generic.sql b/src/test/regress/sql/alter_generic.sql
index f46cbc8..3171467 100644
--- a/src/test/regress/sql/alter_generic.sql
+++ b/src/test/regress/sql/alter_generic.sql
@@ -537,6 +537,74 @@ SELECT nspname, prsname
   WHERE t.prsnamespace = n.oid AND nspname like 'alt_nsp%'
   ORDER BY nspname, prsname;
 
+-- Test generic object addressing/identification functions
+CREATE TABLE alt_nsp1.gentable (
+	a serial primary key CONSTRAINT a_chk CHECK (a > 0),
+	b text DEFAULT 'hello');
+CREATE VIEW alt_nsp1.genview AS SELECT * from gentable;
+CREATE MATERIALIZED VIEW alt_nsp1.genmatview AS SELECT * FROM alt_nsp1.gentable;
+CREATE TYPE alt_nsp1.gencomptype AS (a int);
+CREATE TYPE alt_nsp1.genenum AS ENUM ('one', 'two');
+CREATE FOREIGN TABLE alt_nsp1.genftable (a int) SERVER alt_fserv2;
+CREATE AGGREGATE alt_nsp1.genaggr(int4) (sfunc = int4pl, stype = int4);
+CREATE DOMAIN alt_nsp1.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0);
+CREATE FUNCTION alt_nsp1.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE TRIGGER t BEFORE INSERT ON alt_nsp1.gentable FOR EACH ROW EXECUTE PROCEDURE alt_nsp1.trig();
+CREATE POLICY genpol ON alt_nsp1.gentable;
+
+CREATE FUNCTION alt_nsp1.etrig() RETURNS EVENT_TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE EVENT TRIGGER evttrig ON ddl_command_end EXECUTE PROCEDURE etrig();
+
+WITH objects (type, name, args) AS (VALUES
+				('table', '{alt_nsp1, gentable}'::text[], NULL::text[]),
+				('index', '{alt_nsp1, gentable_pkey}', NULL),
+				('sequence', '{alt_nsp1, gentable_a_seq}', NULL),
+				-- toast table
+				('view', '{alt_nsp1, genview}', NULL),
+				('materialized view', '{alt_nsp1, genmatview}', NULL),
+				('foreign table', '{alt_nsp1, genftable}', NULL),
+				('table column', '{alt_nsp1, gentable, b}', NULL),
+				('foreign table column', '{alt_nsp1, genftable, a}', NULL),
+				('aggregate', '{alt_nsp1, genaggr}', '{int4}'),
+				('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'),
+				('type', '{pg_catalog._int4}', NULL),
+				('type', '{alt_nsp1.gendomain}', NULL),
+				('type', '{alt_nsp1.gencomptype}', NULL),
+				('type', '{alt_nsp1.genenum}', NULL),
+				('cast', '{int8}', '{int4}'),
+				('collation', '{default}', NULL),
+				('table constraint', '{alt_nsp1, gentable, a_chk}', NULL),
+				('domain constraint', '{alt_nsp1, gendomain, domconstr}', NULL),
+				('conversion', '{pg_catalog, ascii_to_mic}', NULL),
+				('default value', '{alt_nsp1, gentable, b}', NULL),
+				('language', '{plpgsql}', NULL),
+				-- large object
+				('operator', '{+}', '{int4, int4}'),
+				('operator class', '{int4_ops}', '{btree}'),
+				('operator family', '{integer_ops}', '{btree}'),
+				-- operator of access method
+				-- function of access method
+				('rule', '{alt_nsp1, genview, _RETURN}', NULL),
+				('trigger', '{alt_nsp1, gentable, t}', NULL),
+				('schema', '{alt_nsp1}', NULL),
+				('text search parser', '{alt_ts_prs3}', NULL),
+				('text search dictionary', '{alt_ts_dict3}', NULL),
+				('text search template', '{alt_ts_temp3}', NULL),
+				('text search configuration', '{alt_ts_conf3}', NULL),
+				('role', '{regtest_alter_user2}', NULL),
+				-- database
+				-- tablespace
+				('foreign-data wrapper', '{alt_fdw3}', NULL),
+				('server', '{alt_fserv3}', NULL),
+				-- user mapping
+				-- extension
+				('event trigger', '{evttrig}', NULL),
+				('policy', '{alt_nsp1, gentable, genpol}', NULL)
+        )
+SELECT (pg_identify_object(classid, objid, subobjid)).*
+  FROM objects, pg_get_object_address(type, name, args)
+ORDER BY classid, objid;
+
 ---
 --- Cleanup resources
 ---
-- 
2.1.3

