Commit d7ee82e5 authored by Alvaro Herrera's avatar Alvaro Herrera

Add SQL-callable pg_get_object_address

This allows access to get_object_address from SQL, which is useful to
obtain OID addressing information from data equivalent to that emitted
by the parser.  This is necessary infrastructure of a project to let
replication systems propagate object dropping events to remote servers,
where the schema might be different than the server originating the
DROP.

This patch also adds support for OBJECT_DEFAULT to get_object_address;
that is, it is now possible to refer to a column's default value.

Catalog version bumped due to the new function.

Reviewed by Stephen Frost, Heikki Linnakangas, Robert Haas, Andres
Freund, Abhijit Menon-Sen, Adam Brightwell.
parent 1826987a
This diff is collapsed.
...@@ -1055,6 +1055,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) ...@@ -1055,6 +1055,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_COLUMN: case OBJECT_COLUMN:
case OBJECT_COLLATION: case OBJECT_COLLATION:
case OBJECT_CONVERSION: case OBJECT_CONVERSION:
case OBJECT_DEFAULT:
case OBJECT_DOMAIN: case OBJECT_DOMAIN:
case OBJECT_DOMCONSTRAINT: case OBJECT_DOMCONSTRAINT:
case OBJECT_EXTENSION: case OBJECT_EXTENSION:
......
...@@ -705,13 +705,11 @@ pts_error_callback(void *arg) ...@@ -705,13 +705,11 @@ pts_error_callback(void *arg)
/* /*
* Given a string that is supposed to be a SQL-compatible type declaration, * Given a string that is supposed to be a SQL-compatible type declaration,
* such as "int4" or "integer" or "character varying(32)", parse * such as "int4" or "integer" or "character varying(32)", parse
* the string and convert it to a type OID and type modifier. * the string and return the result as a TypeName.
* If missing_ok is true, InvalidOid is returned rather than raising an error * If the string cannot be parsed as a type, an error is raised.
* when the type name is not found.
*/ */
void TypeName *
parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, typeStringToTypeName(const char *str)
bool missing_ok)
{ {
StringInfoData buf; StringInfoData buf;
List *raw_parsetree_list; List *raw_parsetree_list;
...@@ -720,7 +718,6 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, ...@@ -720,7 +718,6 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
TypeCast *typecast; TypeCast *typecast;
TypeName *typeName; TypeName *typeName;
ErrorContextCallback ptserrcontext; ErrorContextCallback ptserrcontext;
Type tup;
/* make sure we give useful error for empty input */ /* make sure we give useful error for empty input */
if (strspn(str, " \t\n\r\f") == strlen(str)) if (strspn(str, " \t\n\r\f") == strlen(str))
...@@ -779,6 +776,7 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, ...@@ -779,6 +776,7 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
typecast->arg == NULL || typecast->arg == NULL ||
!IsA(typecast->arg, A_Const)) !IsA(typecast->arg, A_Const))
goto fail; goto fail;
typeName = typecast->typeName; typeName = typecast->typeName;
if (typeName == NULL || if (typeName == NULL ||
!IsA(typeName, TypeName)) !IsA(typeName, TypeName))
...@@ -786,6 +784,31 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, ...@@ -786,6 +784,31 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
if (typeName->setof) if (typeName->setof)
goto fail; goto fail;
pfree(buf.data);
return typeName;
fail:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid type name \"%s\"", str)));
}
/*
* Given a string that is supposed to be a SQL-compatible type declaration,
* such as "int4" or "integer" or "character varying(32)", parse
* the string and convert it to a type OID and type modifier.
* If missing_ok is true, InvalidOid is returned rather than raising an error
* when the type name is not found.
*/
void
parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok)
{
TypeName *typeName;
Type tup;
typeName = typeStringToTypeName(str);
tup = LookupTypeName(NULL, typeName, typmod_p, missing_ok); tup = LookupTypeName(NULL, typeName, typmod_p, missing_ok);
if (tup == NULL) if (tup == NULL)
{ {
...@@ -808,13 +831,4 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, ...@@ -808,13 +831,4 @@ parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
*typeid_p = HeapTupleGetOid(tup); *typeid_p = HeapTupleGetOid(tup);
ReleaseSysCache(tup); ReleaseSysCache(tup);
} }
pfree(buf.data);
return;
fail:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid type name \"%s\"", str)));
} }
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201412232 #define CATALOG_VERSION_NO 201412233
#endif #endif
...@@ -55,6 +55,7 @@ extern HeapTuple get_catalog_object_by_oid(Relation catalog, ...@@ -55,6 +55,7 @@ extern HeapTuple get_catalog_object_by_oid(Relation catalog,
extern char *getObjectDescription(const ObjectAddress *object); extern char *getObjectDescription(const ObjectAddress *object);
extern char *getObjectDescriptionOids(Oid classid, Oid objid); extern char *getObjectDescriptionOids(Oid classid, Oid objid);
extern int read_objtype_from_string(const char *objtype);
extern char *getObjectTypeDescription(const ObjectAddress *object); extern char *getObjectTypeDescription(const ObjectAddress *object);
extern char *getObjectIdentity(const ObjectAddress *address); extern char *getObjectIdentity(const ObjectAddress *address);
......
...@@ -3036,6 +3036,9 @@ DESCR("get identification of SQL object"); ...@@ -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_ )); 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"); 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 t 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_ )); 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?"); 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_ )); 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_ ));
......
...@@ -1185,6 +1185,7 @@ typedef enum ObjectType ...@@ -1185,6 +1185,7 @@ typedef enum ObjectType
OBJECT_COLLATION, OBJECT_COLLATION,
OBJECT_CONVERSION, OBJECT_CONVERSION,
OBJECT_DATABASE, OBJECT_DATABASE,
OBJECT_DEFAULT,
OBJECT_DOMAIN, OBJECT_DOMAIN,
OBJECT_DOMCONSTRAINT, OBJECT_DOMCONSTRAINT,
OBJECT_EVENT_TRIGGER, OBJECT_EVENT_TRIGGER,
......
...@@ -47,6 +47,7 @@ extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod); ...@@ -47,6 +47,7 @@ extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
extern Oid typeidTypeRelid(Oid type_id); extern Oid typeidTypeRelid(Oid type_id);
extern TypeName *typeStringToTypeName(const char *str);
extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok); extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
#define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid) #define ISCOMPLEX(typeid) (typeidTypeRelid(typeid) != InvalidOid)
......
...@@ -1201,6 +1201,9 @@ extern Datum pg_last_committed_xact(PG_FUNCTION_ARGS); ...@@ -1201,6 +1201,9 @@ extern Datum pg_last_committed_xact(PG_FUNCTION_ARGS);
extern Datum pg_describe_object(PG_FUNCTION_ARGS); extern Datum pg_describe_object(PG_FUNCTION_ARGS);
extern Datum pg_identify_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 */ /* commands/constraint.c */
extern Datum unique_key_recheck(PG_FUNCTION_ARGS); extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
......
This diff is collapsed.
...@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi ...@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
# ---------- # ----------
# Another group of parallel tests # Another group of parallel tests
# ---------- # ----------
test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address
# ---------- # ----------
# Another group of parallel tests # Another group of parallel tests
......
...@@ -106,6 +106,7 @@ test: matview ...@@ -106,6 +106,7 @@ test: matview
test: lock test: lock
test: replica_identity test: replica_identity
test: rowsecurity test: rowsecurity
test: object_address
test: alter_generic test: alter_generic
test: misc test: misc
test: psql test: psql
......
--
-- Test for pg_get_object_address
--
-- Clean up in case a prior regression run failed
SET client_min_messages TO 'warning';
DROP ROLE IF EXISTS regtest_addr_user;
CREATE USER regtest_addr_user;
-- Test generic object addressing/identification functions
CREATE SCHEMA addr_nsp;
SET search_path TO 'addr_nsp';
CREATE FOREIGN DATA WRAPPER addr_fdw;
CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw;
CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple);
CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english);
CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize);
CREATE TEXT SEARCH PARSER addr_ts_prs
(start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype);
CREATE TABLE addr_nsp.gentable (
a serial primary key CONSTRAINT a_chk CHECK (a > 0),
b text DEFAULT 'hello');
CREATE VIEW addr_nsp.genview AS SELECT * from addr_nsp.gentable;
CREATE MATERIALIZED VIEW addr_nsp.genmatview AS SELECT * FROM addr_nsp.gentable;
CREATE TYPE addr_nsp.gencomptype AS (a int);
CREATE TYPE addr_nsp.genenum AS ENUM ('one', 'two');
CREATE FOREIGN TABLE addr_nsp.genftable (a int) SERVER addr_fserv;
CREATE AGGREGATE addr_nsp.genaggr(int4) (sfunc = int4pl, stype = int4);
CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0);
CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig();
CREATE POLICY genpol ON addr_nsp.gentable;
CREATE FUNCTION addr_nsp.etrig() RETURNS EVENT_TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$;
CREATE EVENT TRIGGER evttrig ON ddl_command_end EXECUTE PROCEDURE addr_nsp.etrig();
-- test some error cases
SELECT pg_get_object_address('stone', '{}', '{}');
SELECT pg_get_object_address('table', '{}', '{}');
SELECT pg_get_object_address('table', '{NULL}', '{}');
-- unrecognized object types
DO $$
DECLARE
objtype text;
BEGIN
FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
('toast table column'), ('view column'), ('materialized view column'),
('operator of access method'), ('function of access method'),
('user mapping')
LOOP
BEGIN
PERFORM pg_get_object_address(objtype, '{one}', '{}');
EXCEPTION WHEN invalid_parameter_value THEN
RAISE WARNING 'error for %: %', objtype, sqlerrm;
END;
END LOOP;
END;
$$;
DO $$
DECLARE
objtype text;
names text[];
args text[];
BEGIN
FOR objtype IN VALUES
('table'), ('index'), ('sequence'), ('view'),
('materialized view'), ('foreign table'),
('table column'), ('foreign table column'),
('aggregate'), ('function'), ('type'), ('cast'),
('collation'),
('table constraint'), ('domain constraint'), ('conversion'), ('default value'),
('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'),
('text search parser'), ('text search dictionary'),
('text search template'), ('text search configuration'),
('policy')
LOOP
FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}')
LOOP
FOR args IN VALUES ('{}'), ('{integer}')
LOOP
BEGIN
PERFORM pg_get_object_address(objtype, names, args);
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'error for %,%,%: %', objtype, names, args, sqlerrm;
END;
END LOOP;
END LOOP;
END LOOP;
END;
$$;
-- these object types cannot be qualified names
SELECT pg_get_object_address('language', '{one}', '{}');
SELECT pg_get_object_address('language', '{one,two}', '{}');
SELECT pg_get_object_address('large object', '{123}', '{}');
SELECT pg_get_object_address('large object', '{123,456}', '{}');
SELECT pg_get_object_address('large object', '{blargh}', '{}');
SELECT pg_get_object_address('schema', '{one}', '{}');
SELECT pg_get_object_address('schema', '{one,two}', '{}');
SELECT pg_get_object_address('role', '{one}', '{}');
SELECT pg_get_object_address('role', '{one,two}', '{}');
SELECT pg_get_object_address('database', '{one}', '{}');
SELECT pg_get_object_address('database', '{one,two}', '{}');
SELECT pg_get_object_address('tablespace', '{one}', '{}');
SELECT pg_get_object_address('tablespace', '{one,two}', '{}');
SELECT pg_get_object_address('foreign-data wrapper', '{one}', '{}');
SELECT pg_get_object_address('foreign-data wrapper', '{one,two}', '{}');
SELECT pg_get_object_address('server', '{one}', '{}');
SELECT pg_get_object_address('server', '{one,two}', '{}');
SELECT pg_get_object_address('extension', '{one}', '{}');
SELECT pg_get_object_address('extension', '{one,two}', '{}');
SELECT pg_get_object_address('event trigger', '{one}', '{}');
SELECT pg_get_object_address('event trigger', '{one,two}', '{}');
-- test successful cases
WITH objects (type, name, args) AS (VALUES
('table', '{addr_nsp, gentable}'::text[], '{}'::text[]),
('index', '{addr_nsp, gentable_pkey}', '{}'),
('sequence', '{addr_nsp, gentable_a_seq}', '{}'),
-- toast table
('view', '{addr_nsp, genview}', '{}'),
('materialized view', '{addr_nsp, genmatview}', '{}'),
('foreign table', '{addr_nsp, genftable}', '{}'),
('table column', '{addr_nsp, gentable, b}', '{}'),
('foreign table column', '{addr_nsp, genftable, a}', '{}'),
('aggregate', '{addr_nsp, genaggr}', '{int4}'),
('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'),
('type', '{pg_catalog._int4}', '{}'),
('type', '{addr_nsp.gendomain}', '{}'),
('type', '{addr_nsp.gencomptype}', '{}'),
('type', '{addr_nsp.genenum}', '{}'),
('cast', '{int8}', '{int4}'),
('collation', '{default}', '{}'),
('table constraint', '{addr_nsp, gentable, a_chk}', '{}'),
('domain constraint', '{addr_nsp, gendomain, domconstr}', '{}'),
('conversion', '{pg_catalog, ascii_to_mic}', '{}'),
('default value', '{addr_nsp, gentable, b}', '{}'),
('language', '{plpgsql}', '{}'),
-- large object
('operator', '{+}', '{int4, int4}'),
('operator class', '{int4_ops}', '{btree}'),
('operator family', '{integer_ops}', '{btree}'),
-- operator of access method
-- function of access method
('rule', '{addr_nsp, genview, _RETURN}', '{}'),
('trigger', '{addr_nsp, gentable, t}', '{}'),
('schema', '{addr_nsp}', '{}'),
('text search parser', '{addr_ts_prs}', '{}'),
('text search dictionary', '{addr_ts_dict}', '{}'),
('text search template', '{addr_ts_temp}', '{}'),
('text search configuration', '{addr_ts_conf}', '{}'),
('role', '{regtest_addr_user}', '{}'),
-- database
-- tablespace
('foreign-data wrapper', '{addr_fdw}', '{}'),
('server', '{addr_fserv}', '{}'),
-- user mapping
-- extension
('event trigger', '{evttrig}', '{}'),
('policy', '{addr_nsp, gentable, genpol}', '{}')
)
SELECT (pg_identify_object(classid, objid, subobjid)).*
FROM objects, pg_get_object_address(type, name, args)
ORDER BY classid, objid;
---
--- Cleanup resources
---
DROP FOREIGN DATA WRAPPER addr_fdw CASCADE;
DROP SCHEMA addr_nsp CASCADE;
DROP USER regtest_addr_user;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment