Cache lookup errors with functions manipulation object addresses

Started by Michael Paquierover 8 years ago64 messages
#1Michael Paquier
michael.paquier@gmail.com

Hi all,

Per an offline report from Moshe Jacobson, it is possible to trigger
easily cache lookup errors using pg_describe_object with invalid
object IDs and pg_describe_object(). I had a closer look at things in
this area, to notice that there are other user-facing failures as many
things use the same interface:
=# select pg_identify_object('pg_class'::regclass, 0::oid, 0);
ERROR: XX000: cache lookup failed for relation 0
=# select pg_describe_object('pg_class'::regclass, 0::oid, 0);
ERROR: XX000: cache lookup failed for relation 0
=# select pg_identify_object_as_address('pg_class'::regclass, 0::oid, 0);
ERROR: XX000: cache lookup failed for relation 0

As my previous opinion on the matter in
/messages/by-id/87wpxfygg9.fsf@credativ.de, I
still think that "cache lookup" failures should not be things a user
is able to trigger at will, and that those errors should be replaced
by proper NULL results. That's clearly not an item for PG10, but we
could consider improving things for PG11. Still, we are talking about
adding NULLness handling in getObjectDescription(), which goes into
low-level functions to grab the name of some objects, and some of
those functions have their own way to deal with incorrect objects
(format_type_be returns "???" for example for functions).

Would we want to improve the error handling of such objects? Or that's
not worth the effort? Álvaro, what's your take on the matter as you
worked a lot on that?

Thoughts,
--
Michael

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

#2Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#1)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, Jul 19, 2017 at 2:25 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Would we want to improve the error handling of such objects?

+1 for such an improvement.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

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

#3Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#2)
1 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, Jul 19, 2017 at 7:29 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jul 19, 2017 at 2:25 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Would we want to improve the error handling of such objects?

+1 for such an improvement.

Attached is a patch for all that. Here are some notes:
- format_type_be and friends use allow_invalid. I have added a flag to
control the NULL-ness as many code paths rely on existing APIs, and
introduced an _extended version of this API. I would argue for the
removal of allow_invalid to give more flexibility to callers, but this
impacts extensions :(
- A similar thing is needed for format_operator().
- We could really add a missing_ok to get_attname, but that does not
seem worth the refactoring with modules potentially calling it..
- GetForeignDataWrapper is extended with a missing_ok, unfortunately
not saving one cache lookup in GetForeignDataWrapperByName.
- Same remark as the previous one for GetForeignServer.
- get_publication_name and get_subscription_name gain a missing_ok.
- getObjectDescription and getObjectIdentity are called in quite a
couple of places. We could have those have a kind of missing_ok, but
as the status is just for adding cache lookup errors I have kept the
interface simple as this keeps the code in objectaddress.c more simple
as well. getObjectIdentity is used mainly in sepgsql, which I have not
compiled yet so I may have missed something :) getObjectDescription is
used in more places in the backend code, but I am not much into
complicating the objaddr API with this patch more.
- I have added tests for all the OCLASS objects, for a total more or
less 120 cache lookup errors that a user can face.
- Some docs are present as well, but I think that they are a bit
incomplete. I'll review them a bit later.
- The patch is large, 800 lines are used for the tests which is very mechanical:
32 files changed, 1721 insertions(+), 452 deletions(-)

Thanks,
--
Michael

Attachments:

objaddr_nullness_v1.patch.gzapplication/x-gzip; name=objaddr_nullness_v1.patch.gzDownload
��pYobjaddr_nullness_v1.patch�}kw�F��g�W����)���~�3��H�G;����Mrg���(qMZ�f��oUu7� ����O"�@?������a�g3V���=f���+��O^���|�A|��l����|eZ����l�&�vsZ�O��Yw���]�l4z���Z�������������e�V��������Gw�7�+k��]�����O�c�{�a�]��h9+ci>a�s���`O�&�{cy����Y9|��a|5^�54x�Tx�*���M�U;8����k���w�3�3~r���T�;�P��f����P^<g���������aZU�C`�
L���h�O��b��y��V�=����������^�w��u����[��������������������l���0d�K�.4om������tZ�z�i6F��i����k%L�H#�n�:`G��k����CV����gL�������r������~#�@i����OT��*{C�*T�v����Dz��k��dn?���|���������o_�,�.����U��*]����$�Y��wtI���((���1�v���A�[�-�=�w�I�In+LM����������~���"�s\EU6��{�/��c��3-����3V�����1���@~��-�
J����P�}�(��p����d[5��w-4^!O��=��&�o6�%��(�<)�1��F_��&������z�X���{�����,�S��g�W���;���>5�]����:;��?��������e,�f�����[��2�������Sl�-l��f
��@|@0T��?m�V�����q�YB�%���[������!{3����~zK���V�x��e�,�hqI���2\���%���y��v<1��P������R4��i��7�N�>��h��s�a)FZKY���HJ�FI� )�":7���*W��s�����c�^�@t�(�!)�
5����@5l�F:[���q��@VI�l���.\;��_<��������"B�������4�h,6�[g���,"�
,����q���&.(>������M��W��A�8���F��)y����L��
��+,kl%�V[h��a!Va]US)�[�i.j"�S��������ndj����z��� B_�@h����Z1�~X�B�'+�
���������{0\����uM�k/�P�3��V�ERz���J�u\����;&l�������M3�	�}7@!�N#���R�Y�7���Q�<��]�9�eU��#|��9��n��/�|y����f�C�o�rq�s�����5�>|�\�p%r��.?~93�g���������nD���s]� ��Yh�����l�5}������Ns8�X �h�M#��FS�d�N���>�H:��X������|rNdaOP����s<f�_E��l&T��o��9��xX�'p��M�@2�+?�*�]�|��R�<�(���@�y�"V;�������@���(�`|���9���+G�\���]���=����8>;�=���fT���7��$���cx��x��w�A����"��X�x�,�:�R��KQ�������	Bdb3�^~V42[��r��g�\��I����V�W�����p2h�[X�O�b���`��ED���f���G����u"����B����~��>p����������8XOS�4�������>On�//^r���WR/'S���"��j���zX�Y����g<4��9�u��96�^��o���H8�J����]N��XP��d��'V�^�OZ�i�9ht��lA���[){�j�"%|�2U��ly����������e2�w���H��@l��7�P;�o��X�������ip]�W-�����)b[`��'*�/@��cO-s�XR���Z)�P�l���������)��H��L�b�~�Fw!�l�z$���F#;���������HSH}=�.n���<����6���
�`:
d�]���0�w�\8A�(l��Z�h�!�V�33�����^eN�3k��[��h@���K\X�2������a�1��S8�{W��Y��3�Z�����[��W�CO�p�?<
�`��vy��i9�#�a�zC�L�SBe:X/Wt��f����(��%��/�UwmL#k8�~����+�/+Y)m�>�-���Z�n��W��oN�:zwy�?�O���'�F��L�{�$c�QY� ;������f�Qm6sb=�v�4"\;��s�A��z��#Y�h��%^��������5WS��3������)�e��Q�gd#uN�A�k<;|�����?�v�G-B��!��r�x��e����(U~������g��a{H��u���2\�O$�j55�U5��h
�+��4k)doW��f��li`�*�����t�n�;}�����>Y�M���=���F�)��������h��B�K�'���4j
���m5���������J?e(��OJ�A�����6p��'@%1�Mhv�	���,'�#�5{:��5�@� �#�a��� L��f�dRp�1�U���+���������s/��*��"���?��?�O�!$e��"�T����5�����T���U=��O8������HrT,AG����9T7"�t��cY(;e:�����5��X�u������������!�����BZ����$����,W���hw����n_������^Z��t������E�Kjw]2�v���[��E�P�x��4T���o�������a���u�A�6�6n���3�m��U�T����J�/�4�7�a�KIR����G.�}z����Z%x�(�D�����$�R�OL�3�����n<D=j�*,q�t�j
��p��x�.����<\�o�w��zO���1yk��k�n�p���N�����n�9&�}��)������L/*R��Kz�9,s3�T]��� �J�t��n�?�w��f�3mu&�$�� ��^����S�x~�0����>���J�%t�\�t��4��Uy��<���y�[q@ecX�����uT5j�������
�_����7�qn���1`cz�1>���p��n�x13������:����Z2��e�n�k���0�F��5�0��S�pF��X�'<���x�����!�UE�~��yp��M:���?�
�8~��}�Kt�S/<�����Ojz�T���>�[�������p(~�������]�b(��8+���.����*=�w���k,<�#��'w�2�l�.�*p��F�{�����;�D,�4����D/_ro���a���z���Z�+�mYi��7�#t�%w8�9��9�����NoQ�#4?�G*�f��������v�����l�T��@e�{~'�`������,�U�����`������c�\��Ba�gZxK@.v�[����n�����[�o�Z��;2-7��E�Fs�P�=��$+$g1��	C�bOjH�
��wc>�0B�d��Q��zB��,�0^cq�<��/���!����`&��Ksr���I�r8��'~��cpm� C
�����3�ICY"��%�q>0qlE�0K�"U�0��������<:�^�
DM4����^�z��
�JQH
j^�v_�uc,p^&U�;"	A�#;�S���b������hr	8���VS�O��b����?K�C
E������8X2��3�p�:+^�Z	���|�8��`����������Y�!���obL?A���/k5}�S��"��f���5l��z�;�Z�1�F���Mq���i6�$��G���X�g��]�������C��o]�FU8L��=�th	��C��B���KcklV�R1��R ���0f�E�Y���������Q�1�6;Lm���J����S�e��~0���~�r��A��x�<Tdt�� m�f���=x��#G���:^�Hyx�X�3���^����]�.�F����X����������������"��K���^�l��~����M`j�d�_�y����{�2�����G��~�#QB�M^��/�$��?�9P.C����S�n�@DKk����p�t���k�}$�jk]<�����\��K��������K>���@���i�,{�d�B����9���F��fa2$�{2Oy�`��g�s�����R�k96���o�?Q��X�������%45��8�>�[f��G��i-a4����& ]�P�	
=�.3��H��|�B|+8���H>��3��c��^j`�YA_��j��@'J7(2�g�>#�Q�:�7��j���$��p��.�#�r�P�L��h�?��%�7��Z���������d�0�0���oO��Vq;��9���M�_����&����x�*���ri�s8}/_�Bw,����zF'tc��7'��b���7v�}���E�m�:��sg��/����:�M�Z��}����BE<���Z�p��apny��9nQ��
l|���A/�&7�}��$��$%S
5�iVH�[Aaj��2�/�o�H!��)U������
�t�=l
o����C *=��G��7$�c��d8���K`�l��K`��]����_`<��*3<b;pJ������q��G�~f8��*�3
�*^_A�>�
��������*��Rk�� 4�@R���Dh�;���^����Z��EFT�'}c�%��,A�I24�!*d�Q��7�Q*�h�
���j��&�X��b�af�N~�^]^���p����"�m��T�m���'B=��[
Wi���@���	$~���+�A���+j'����@V�4Z�GA�����G���TN��E�?�A/l��l`����*O���)QTG9@St�����[RY�.y��9�k�Lvzrszr6���rj���l��T�>�(�[�|$B���S���je ��93�f�>�;V����������5���t�����}��$�3��4���:�1J_�E��VxT��t��a�'\2-�{�P0��N�����;\Zb��8���>�����6���J��q9�lf3��(��M��M�RS�te�����r�x������y&��}�>J7m�kM~h�����v���f���8?Z"�plmcx��L�����s_jN"�m�q��K"�"�)d���'�2�n���r�M�@x?	��f	]��b�l
�$8��
���C������a$�6��n�P�"rD�7�r���E���*�Q(D�zp���2-�LWKTN����<�zj������)'���I;��Do�%����j#�n#��������HW|�Q���������d�?�U��Q���D-���'���%���p_��!��V�@L�����8�Z�����HB�&�+��/����?�,o8����������D����X�mb���U���������
�,\��$;y�0rA�k�Z�_I>����%�*���l����T�,��x�������s�&lkc����L�3����Bx�!����E��2��F�1�S���'�����?��5�V��4��@��2��)._��D
ty��V�/W*&��?�]]�+P�����zC/p�����8�����-�+O�+&�T��>osS��E���F���U�f<rQ���-<�������@Yd���z#�g��/�d�:�5yc|����(�1pGyj�WX�!obS��bEE6, mkl}�v�[�H����>9�@��g�bd�A�oF4���s���\�2��4�zx���$?4�@�ZJ$Q`��}�
E!-���`����	��'7�/�\�7T�]L���!L
pK��y t���6"��
 Wq��J��)-�GdX��-P�<�D{����2�������K,�:�s��s��Ia�&s�,�	�C
J>p��2�q��k����7���^��R	�����ihY+D��YX��( -|}O�����U'S�����m���x8�Y�ZMLi��w�������`/�D�7t�!�5+hY{y~�,�:7�X���m�Z�r��%,��?����\��a�C.[S�\�K>����'qK`&�����i�GZ��#�VI8������;�Vp&�`��
�L�F����q%F�	e��N�k>�x���E����R���b	�����y��e��Q�M�*j��t��eqrV���A�\������z�XJB>���� �����	�i��_�
��@n���-'0���JDa�*90(M��'�>��a����0�#Z�A��f���YX"����%"�������,W
�	t�G�%�^���)��=��
�EBY%��,N�52#Z:u��[����[�(�
�*i�U��2N�V��A��w!=g�86g&�]l
�-�V�V��T�u��0�U���(W9����\�y�&��{a��6h�9���F�K��-v�QQTB������%�2�#�Q*�&��������+��A|���;+ l'�� ���D�����]��^^g����I����s�b�uX#���JPj��3�ExrL�����+�V�-�N�Kt��N{=M�����.����	tPYm�����TDs��.F����"'� ������~/�'E�����[�
�3����m��S�C�������d E�T!�$�a�Qm���j��(]�^�I�^�k��:��kD��i�a��F��z��i��/Te�3�=�rA�/������`~��y����o�g�x
���GV1�����b�������8@��3���]n�M7�T��;&�8����5��*����s�~|� �p�	�8�1���� 2���|����C���eL
\,E[���f�I���k8!#V�pMJMC�Vn	�:h��*�v������������ t��O��6�8��
�u��b����K��d�$rW��$zO�H$��UT�W��$o*��%�$}�o�t���S�w�����F�w����s�m��9>���';DT��������_�������h��F�O����X�����l�����p&S���+Z>���U`��/������!����Db����{P;����{�D>O��+�^*��2j���ii>mK!�<�
m~��~/�6�����MR��n�wf��l�K����*|����g�h����,j}>���vy���c�+��rN5c����c���d��B���I����&�.j{���x�-�T������8����r��8W�QT���3�������9��{w��\V
�"�������F���d����"4����x����v��+l��{]�k������	�SE�\
T���
D�~� G.�Y��?��k�fe�z��j���������_�srr�s���U��r��kT���K#�?�a�tA��������o���������(jX��PTa��_�t��a�=�����a��`���`��8��L%a���4�b�2�9N��������X^�I|��-� e���Qy;zw��o�-tz�rM��{Df�GT��tz�&3:;be�h5��������o�-d����������W� u:�������` �C��T�����#i2���j/�H�<�=l%8����<����-vD����X��������l �mI0x���	�BDi�������u�P��d�����kE���J^A�A9�D����r�������d�O~tb�z$�u�1������2�&��K��@��%�����i��&���|�����/�xA�3�`�vj��~]��C_�c���x��_�=i>ds��<�d�0���V�D���)����|ADdK)�������'*2�>T��-�H�x���@��V�"��-����������o@��
�-��S��Y�f9������hR���J�5G#v^�v�Yb�W����h�BG���P�$i�3��n�/���)
a3I�"m�ON�f��T�&k��{EE��|��U\G&��E	����"F;��:/a�����vg���pW�#26F������ka�����+
g��K��A���5�pD����������oA
f}��Y/3�B>�����O��8����Z�~�Yj#C*���#]%�����jia��Jk{1�f6��x�Y�'0.B�A�`C�.w�Yn��S.��~k�k�W�d�t�V�q����)il�����9m0��f�M�������Hz��it��[\����������Ev��z3���j����Xy������+[���:
�h"��>��S��v���F,
���FA����W�gT���Z;Q�YE-����$�^:9�f��^\"_�����b<0Y�/�`�����s���'�!��u��������y������U���n&�)�nu�2-o7����5g��;>~�n_��F�����E�3�_\�^�.���w���X��0��i�2zY�7Z�|h�9��3��;����=)����#+f-���;�������� Y���y����')�>U�C9t�ni�L`�����(�j.�P@3m��'{C��3n'����n�Y���8�����
�[��D<�Bpr\�Q�}�|��+��D&��~���$�@b�z�wQ�(��T-��p6���#OU
�7bJ�>+a��38�������>�$����4�}�l~y�G%.��Z���O��D��E������35Bl�!B������t���m(���S`�;
�I&���O�����yo�9t��R�`�J�L4%�H0�N!�BA��>�bL|!�O����O�� ��~��s������G�!�w[��j'o�t|�#��������� 4�
��|�.�`2�Os�wY��HE<��I�����,�q���vb�&�p�_�����h#���FD�:��E���������(8�O��P.=��_.��|��L�(xc���a�����c|T9�{R�
��W�)�V;��1����n���;���w���f.R?�����S�R�*�J�������������(��K��
��>"!]������x�b�
��E���3�B�U�N��<Q��;��g�-V���?!`��\���\'4>�U/v&�!/$�i�0�S�h�"o'�*}z�&�i�N@|��
�Q�vK�(D������c������@;���?E��G9�������NR\$l[@8"f[�7���~7'��Z�1�IR
���0~���Q���(H���`KV�b���|T�����=WP�r�T_tv��I��|TUF�	������~��w�8���+#�k��A"�d��W�|�����$ib(������^0c=$�����s���V�<h����s`�����2������>����7���7�i4�H'e9^f��W�����6����+TN;h4�����v�aJ��P�X7x(�g��B���lX���? ,�^�D`8��X��*��!��z	PY��0D?�� t�l(�9�%M�g+s�T�fF������Iu0�m�@�$�q��8D
����'���&3e�����\w�i4�Q�Mt��p���o��1�y�[��
���d��R���'��3/����O��v�5tt+x>�	)D��1|�0_�B��	C�Ze�\Z���"%��kkU	�BV���d}�����&~/�p&������8��R�#0���5\	���f�I���%W�4ZMB���U0���f����r���_�����9����-���F��E�]��@����:|�������i��a�����
��=@y�If���GK&�y���Q�J�+���it(
���� @�(T�"-l�4�*sm6�Q�%�/Xf�����Y��i30�:�G����/�C�0,c������l/��	�.=�i�`�&��5U���������]�I6t�f��V�
�M`���3&v+���)����s��,�.�k����W'�K�
���(eT�R!V������x��eA���I���a�c3��Bc;���\��o\k�Y�&���0�&� <��2�Uy2w_����@�q_�j���8M��~0H�gUE�Wmw�*�K��%�m���)P	$'ad"���U]��7=?��(��tt.�"+������k������L�C��E=iz�n���N�|����8��-�B ��N
�����\��+�}R��S�*��X���V)���O�\��LE�u�b4���Y^�$��M�T%���+f
,�T��L���kdMK8e-f{�e�_�����*	����]iQ�H�U&pt��IKq���`����oN|5I�w���C4�qM��~i|�5�w�<���\��[,��,�u�m>�G�u9'NS��iol6)&D]|"��\[��4�K9��G 
�KvE���+��H�����A��A��]���|/�z���s�(�,g�(q���2�rA�d�
�(n��8
{�2����������������N�[�G��`J���y�p7&:i�*�Rc������
�/��Q�m�����T��r��:y���2"jtAD��nuv�K�u�(����%�z���V\%���5�`wA��N�;��QE��n��\�(���+�S���$�*�j��z��
�6�S����4�:v�������#e�:r�K�{?Y�:�~���A4~Q^�������5�/2���q� Rn��U��Y6��]9���U��I d��c������&9*u:���pX,�R���i���FIJ��6����MY��A�����m���v��h�Cu�
~�����3yy���JN-n����X{9N������'p��p�������h<�TI/3D)��@x�LPY��F�N��9���_�����~��y��F�(��JK~Q�"+G�����c����!)���)w�w�����6wH����8�b��""O��~���$z{��������+?I��c=9��oqf������hd��x4���)�>��*R7��|�����LK����i��j��f�P����\c4d�6=�ls�/�l���3m$�|��W-�dcS�wk����x��>7�����>�YZ�V�#�6�?��$<� [0���c�@���7��%�������v�z����������z��������`���UV��>'By�~���P4�N?���,l~g���g�@����9q���������rT����B�w=�~�#�L�~���r/�B[\:�w���B��?!:���T�>'B�Y?T~tv�wB����t�5��a�����C��
���!_EMf�`40~6���YD��Y�������H��l��
e�����@������
��
�<s�Qg�V��X��/O�M@(f}n�_yd��r�w�i���^n�[^k���q�_]\�No��fv�v4g�N������Z�A4�~a��4t�c6i2��zA�q�E�'���7��:�������I��w��DN��[{���#eA������l�����G��MZ�u���mXd<���p�w���3��+���^T�&*(�,O��$?���������������R7�s$],�#���2f7(�b1�A ��b,s@�����%�0��&��`�F�_��k~Dt_���	�I^�b�/��q��
>.����$�+��-����"�]ti�w�I������
�G(1Kbg�eq�����M.�v���.��KX8Ln\|��$'�5J��n���M�P�R�(+u�����e)B��R7;/��1���)�M�n�r�h��OhM�BC�i�&{���B=��=��M����;�q0�{��g�+��9�>�y�Q|���r=�������������c���9�WV���,f��gt��~bAM���q-he����0T�y����Ec��h���Q�.�������)�\�'b���.�S$}��������>�K~�
EQ�������SA5�Bn��0IU5�#�D�P�2ZTZ�I5nb4���?c@@5[��{F����*�������ZrRb0>��v7�R�����	TF:f�CY��H/�XBz�n�lW� �����T�o�<��!�D�8���q������~'y���z������� �h���*����/��$�5��U�&1�����p�x�:�D�ByAJf��S6�V�	H�'f������~���v��Y��n�f���t��j��}=9::���_�Kdw�����t�3*d������
V�H*�����������W����<+3iE��X������z�N�c:��y�_p9cb�����ZN,�o���UQj���vD�����\1�����/�ev�R0�
KH�"���0++(�Pbf�tQ����R'0�6���Q��b`J�Q��
�1P�U*��Lm�����g�����1:�_\���/�W�����?�9���*<8<3��t��!���0���H�/
��b��A\�i'@fU{_�T`���G�nR�L;_m�E�����*�'Az�� =?���>�\Z��e�3�]>���
����B�5����$�����	�0Y29�(�	D���E�����I��������I2��H@u�%������Hg���}#��5����*^*���G2w���'<s���	�C8 �1"��u����
'�{{swko��Z�6E1��������m��c05��zg��L;}�������K��Ey��!�2�����,�BW�N��j�XMD�6B��K����P�Lh�[�*)�4)I�L)�=�����5�pA"v��d�K��R�����a�`�T [s��#\Vq.�I����&�/!���;��q����o�v��dYU��vA.(#�Ss�M�����������L�Ii+u1)��8�����?[M��X��2
d����o>(P4t���|k�sr������|�a!r�p
$�����'������l|6�]��.N_�_����7����M�yn�R���������WvM.�e��,Y:+.�����h���Ul���"�
�S�o��G?����RUt?4{��Po5*a����s���"f��.$A�"�u�1���u��I�G�X������z�c�;W����[�<VM��I*gO��^8�kVq��Y���(6�R�0��a�ZY�F�g��Y��%~�����f�V���^=Z�f�� X[��`�E��,.P����^Xs*���{��f8_���s���FO6�������U	�\<e�^'|�����*�}���
WH'���T����R�C6��g��
*K`_�%�H���!<�uGOK�Y��
��o���j�U~�`=���n��l�E<*/��%_[�E�VZj������h�}�T����x'
v�����z��L��� ��Byd��O�f��XP���h������p?����LyH���Tq5TN���I�m��S�"�t�t�f��&���K[�ei5(����]x��n=����c�����(�90~7���U�)%��]�$��6�w��@�%o��xsz}~�|t��Y0�:����?��t�A7�4������4h/���t_�Ll�+,��X`����nO&V�^������������f}%%���)8}�C�K��B7���<����>M���������?WvB�VReJawk�U*�In �J��t�Iw��Q����iD<s��e�q����aV~�L���
ed���6�%�U���n�F�1$\�g7�����4c�p}Z���l�m}���9���d�P��E�n ���
��8�]i������?*O�
�Z���
GT�:^)	�����'~����o���a���f����;
��z}:�Z�~����0
��4&�#����;�/-����g9������m���Z�m�@��8�R�s!-����%��������s������a�{l/�w�m���|�w"���,����\I����*�������?
���w �l�/c�mqJ$�nF������w'�g��"��p��.>j�+m!������
b�X���:q��0��Kq�����W�KQ��0@���C	�I�O���r�����t�IWx-�k�^�!��G��ixI��+�>�xK���
D9�[�rvF����q�T�;O�3H�
���
lde�����&����3���n�����FO|��o��D�u��>��f��3s�%Y���49I���?d���������K�g��W1����J�F�T�9����}��A�zrU��'RE�*,^O���rE!)H���z	h����K�M��B��q�0��f�������%��`�A

7�"������+~|)(����EgL���T��;F^��QM�}}#U�������|����E�?�H��LwwqBu>�����T����oN�����iXdP�����/��0��$1j�:�����x���������������������OU
�!-q��Q�M���L/Gc%�(`w����/��������f�:���Q��!M�F��1�^h�]my�l6d~
�����F��HR�x�J�IF!����Qp��-3"
%O��a�-ibs�Bu����f��u>��o�B�Hd�N9� 2c��m
�O�DN�����c��&���=����'�J|�
����Z�N�������`�H�+G��-z�U��y 0A>+%��dY�����7c1h��)DW��S�������9�J�l���K[�A���V��(HC�YE�,L^�s?doIF!���|�-l8������H	����sG���s��;<'r���������d����7h�,�2O��BV��"�d�h��l��C8����l�AQ�J@�bHF���Y�#��~�0?)�����r��<��,��%�><���,$��u/<�@v-�U��e����!��T���~L�/���� .���OkxE�%��";fs�po���^������nV���M�W��f�|��2�m-���A�����Ej�.���^�g�a���QE�����G����aQ�lW�������������	��k�[���u��?���zp9w9�-l���,�0��5p��z�F.G����[��D����v}G��ScO���R�.m]��e���r�e5L����E���=��;�+��7�0���d&�����P6"	$oUAz��B��n(<M��{#&�	�D%��)�����J1�����_��L���_��M��� Sed��V�(A���@������.����aCK
6�;x��Xz@Wo�
nrBv#��X�<��2������kas���&��|��8
��b$�n�0G�t�6��y��j@�+ k\90�tK�l�����hh���[��� ��=�����~{'�����w#�����Wc�s�I=��L���X=���"&v�~Qj�����F���_�(�P����F�����5����Fv��������Kl^"*��{�X��1X��o�/��r�������'���U�����{l9����;cm�c����&eL#�zP	���Zv�~�5���u��r��b��p��
��x_�\'�"u���+�z���|=������NR��4If����=����b�����u-	~#\�l���������`�jw�Nh�����n��7�8���|trq�����K
W���p�
���7�?spt����oB�&��vSeS
�K��UX]A
��,���S�s����:T���d7�� |�l�%:��+�8��'�U{H�|��VK�VF��ke9��%�l�htz>�w"Zc����4w���K6�&;�����3�Z��]��@"����_�B�+m��g�V=��D���9����l3/!9�Ex_�0��V����/����4$�0?�=�>q^>�����`{i-]�����h���`�k�3��Z��E3���"��@@X~���b��%��l�'�n�a���i��n4��|Io'~C/�-��8�'	%W-LY�@u�m����	��^���(_:[�'5=��������-%��n�4��`���b��z(<��t��%g�`a]�<r�������O�'WW<8}�/�������-��='X�|O��1
�!�D�m
zi����X�jg.�t���@c�d�z�����%���+����#�ZU��R�J��s����Im'�zo����{����:�Df�@&E�7����TQ�B�[$!�1����&��e#uA�Mk�5��5��.f1�Y�~2%�����ac#=�(��17_Ip��m|�U,q����p;h�U,6n��mv�L��F
������7^��+p��WX��a��n�4�����az�/
o�*��L�PJH��dfL�����g�Y��j��d� ���|�T�{�	��������3����<>��m��oG��D�����m�����������^���&����+h
b��E�B�������k>{D����v����54"Y�D8����8�$|�7�����>���~S�SVf�������a���1p�JC���� �W�<��oS��a�Z�N��C9E�m���i�[�+�;0awD��B�#��i��3
Ib�����N0
� a������cLh?��Ly���g�=J�E�0�C���\����<4�����|f���H�B:�C����������yk&���X�sO�����?���7�N��N?yb:@��Xi'���$��VBz��Z"���Yp������������
��S{��������v\K�����I-��y$�i�Z����J�m���.�Y,i�{2��&zW��c���*ic!�jt(;	F1������c�q��P��f��S�2����sXb;'b�A+�����������SC�A���l���9v���n��N���H���[`�&�����>�������O�)���RR�m���p�����i��zf���&�����!��0���{�]�,&�i�)9cg������k���� ��fa�K
R�����/���GG�G��b�=�EX���Y�����C;���Z���o��p[O>�K��hKO���{�I960G|���HD���y�
�<b-X#��u�v�i�A�/!���5h��I��v��v�)�c]Ki��R���!��6_M�bOyY��i��Gae�;���^[�D^��S�W"E�~��p ��q"�:x�������
��Q�q�����R��B�8/�����"�< ^��;e����������x��7�-bQp8���g8|ecS��LXE��$^�����U�$�7"��1�	o������}�u�6P���f����b �G<0�+���v�s�HM��������-r}��(V�_Q�~���xX��F���������3	n�����3����s����%��0�G�Emw���P��jJ7�A�l���
M����� ��$���@�8�C�@��S&.�����G6��5������yL~��D�������X|�ai���q���t;jc���"��!�F��l�������o!-�"H��A<[�t]�!K3M� �:��J����0^���������	E���*O2~�c"����;G"im\k�Y��i }#�+M������
�]�c&�=�V�0��k���g����N�w�E�5B����+��E�����!�������"��n��
���������|r�V��K�^�=&��q�k�m�M���t�1'������GyB��<,$����BH�VLV��`�kL�z��1����irblm.��%�1�e�����
�cT�@�9�y�����Q\���e>��;&!.>�������8@����[�V=�(��.7�p��6BB�s�+��I���)d.'�����f���������5���N&:���JH����(�����������z�F)�CP}����<�AZ@�W~t�y�XV�j�ahD�!��X�_����\���*��;8E����$&�.���vVQ����$5�L������l�]8��Lq���I)^I�7�tV�#ND��� ��C��5�DN�#��"�Q�jiT���uB��� 1��EG��g��nX�X!��mFd1����:��K��dfkDj�<�LPFc")I�+�&����nl�D_�������������]�;�6�������L|������i��c���q���%h#]����Z�o��6��>-�[�_,a@2_.E���e^��[�;^����r���m�BO�^��n��
M-�oe�����}�;������;����x3I�$jP���'����
�pE#?����)�_it�I���>�V%U��@_�*^K�fb��)z�d���Z��e�T�&!����B�I-fT!��KK��`n��A_l���
�^HG���g��k�xbUd-.���Zm��}����5�Z��f���l�>3W�K�A��fG��Wb V���QptcL�%Eu��W�������������.Fg��_��I�d	� �{{T�����;yj{�����_�o����6&���euT��	b���#�_�>9����������;<��0���1���L&y~��%�����F��
s����#~��OZ��\W����A�?�������'c�/����&s��C�x+8p���������)����hnr@b�����?Y���2��TC?"�hp�#1��T��T�������|�`���-����q8������Y�Rq��<f|[����r�l/��H�,B���&���C�]�d@�r@�f#�|{Dc�#����9/>�"D6_����-k�<�������d�YQ��}b[�&�"��R��0Vw�.�S����8�����K��sg��iS�BB`���L�K3�k���4�l������(K��-�p)���[�7Q������mg2����0}�mJVl�e���j����X��i��%�3�7��s����0����am|2�OI�����:y^x�������E�5����K�0w,�����e�"�Q��
�&�ah�rN"�_���@���"��BD��2��'zJ��H~��!��g�7�������S���LBs�������f������������5��no�=��w��6:��E�mY�4�i�%�CZ������3�>e1BY����p�MY�0uYH�wh�f�}����fg���.u����Q�q:l�k�S�K9������){��(q��K��d����(Q����^
��R�����%h���hjL����>������.�M�S�V )"�/W���.e�BM��<,�D���S�N�a\�������NN��Z��eS��0J�V��5qR~�=(��l��LFM��2!J���k�~�~J����83��T� ���2���6e
A�=-!�����V��������������b���q����b�j'%)G���f*i�Qd_w�~�fT��B�S�n�/&V1+�x	DeuH�<���y����,F��aO��dR�/Le1
�W�����6��w��G�;�=���������{oc��
:��B��k��!��?�������@s���k���L����|�mY� �4�w��������iy���4�"�j��G�[���"�������h#)p{�/���eO����:�^e�iE. �<��{G!���H5�*~O#��=��(����4��:�}��_����R�K0*������_�Zx(��O�$MT�
W��s>���D���������F!���i�����3�u���7������YU�3>��h�:�}����V��nh��h��fvJ+g���2L(���.j%M������Xvs]��������2�R�2O3�L��J�lQ�����6��k[I�*��-*�O7���[��:����>o%�2��V
*��\���/�����?\��f�i�'�.��w\��7�_��9��+��0��7V����2x����~t%��a_�R�Q��.�������L���]�	m�O�������q�w�m�l��]9tY��]n��bS|���uY�x�l�<��BI�y`�q����_����6��z�@$��^�]�F[����_���Z����4�����������f���W*����#SE|�J�S�%�����0�]}�O3�^4�X�$w�,�.B�X����,S���+X�t�{
��d�����eW��������I��Q���2���`��_X����1,e��z��v5������v]D�U�#]lk}I`{j�-���:�z}�5-��N�m�ZL���P�j��V?��~V;1r��������hH�R�A������{�f����{	���X��G�����C�~�����:-;�h��=�
�Yr��#C��q_A��p?��lo�J�Wz���DQ�W ��D�[��=E��C��=��*5r��)}�@F{���P=�L��/���p5���=tL�`�
�Rn0��C��e��M�K�����"��@���()�FY�3�
�QZ���Q�R���e�w�C��2B-�0����/(7A���P�S�>\��q�/�M~��������x����-��}yK��y���������_����>�=����>�����>����X�G�������j~P{tH��A��������}I����~ ����o��/`��o��/a����Z_�}�����~	������m<�
#4Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#3)
Re: Cache lookup errors with functions manipulation object addresses

Michael Paquier wrote:

- getObjectDescription and getObjectIdentity are called in quite a
couple of places. We could have those have a kind of missing_ok, but
as the status is just for adding cache lookup errors I have kept the
interface simple as this keeps the code in objectaddress.c more simple
as well. getObjectIdentity is used mainly in sepgsql, which I have not
compiled yet so I may have missed something :) getObjectDescription is
used in more places in the backend code, but I am not much into
complicating the objaddr API with this patch more.

I think the addition of checks everywhere for NULL return is worse.
Let's add a missing_ok flag instead, so that most callers can just trust
that they get a non null value if they don't want to deal with that
case.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#5Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#4)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Jul 20, 2017 at 4:04 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I think the addition of checks everywhere for NULL return is worse.
Let's add a missing_ok flag instead, so that most callers can just trust
that they get a non null value if they don't want to deal with that
case.

If you want to minimize the diffs or such a patch, we could as well
have an extended version of those APIs. I don't think that for the
addition of one argument like a missing_ok that's the way to go, but
some people may like that to make this patch less intrusive.
--
Michael

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

#6Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#5)
Re: Cache lookup errors with functions manipulation object addresses

Michael Paquier wrote:

On Thu, Jul 20, 2017 at 4:04 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I think the addition of checks everywhere for NULL return is worse.
Let's add a missing_ok flag instead, so that most callers can just trust
that they get a non null value if they don't want to deal with that
case.

If you want to minimize the diffs or such a patch, we could as well
have an extended version of those APIs. I don't think that for the
addition of one argument like a missing_ok that's the way to go, but
some people may like that to make this patch less intrusive.

I think minimizing API churn is worthwhile in some cases but not all.
These functions seem fringe enough that not having an API-compatible
version is unnecessary. But that's just my opinion.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

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

#7Michael Paquier
michael.paquier@gmail.com
In reply to: Alvaro Herrera (#6)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Jul 20, 2017 at 6:26 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Michael Paquier wrote:

On Thu, Jul 20, 2017 at 4:04 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

I think the addition of checks everywhere for NULL return is worse.
Let's add a missing_ok flag instead, so that most callers can just trust
that they get a non null value if they don't want to deal with that
case.

If you want to minimize the diffs or such a patch, we could as well
have an extended version of those APIs. I don't think that for the
addition of one argument like a missing_ok that's the way to go, but
some people may like that to make this patch less intrusive.

I think minimizing API churn is worthwhile in some cases but not all.
These functions seem fringe enough that not having an API-compatible
version is unnecessary. But that's just my opinion.

I can see your point. The v1 proposed above adds quite a lot of error
code churn to deal with the lack of missing_ok flag in
getObjectDescription, getObjectIdentity and getObjectIdentityParts.
And the cache lookup error messages cannot be object-specific either,
so I fell back to using %u/%u with the class as first identifier.
Let's go with what you suggest here then.

Before producing any v2, I would still need some sort of consensus
about a couple of points though to grab object details. Here is what I
think should be done:
1) For functions, let's remove format_procedure_qualified, and replace
it with format_procedure_extended which replaces
format_procedure_internal.
2) For relation attributes, we have now get_relid_attribute_name()
which cannot tolerate failures, and get_attname which returns NULL on
errors. My suggestion here is to remove get_relid_attribute_name, and
add a missing_ok flag to get_attname. Let's do this as a refactoring
patch. One thing that may matter is modules calling the existing APIs.
We could provide a compatibility macro.
3) For types, the existing interface is more a mess. HEAD has
allow_invalid which is used by the SQL function format_type. My
suggestion here would be to remove allow_invalid and replace it with
missing_ok, to return NULL if the type has gone missing, or issue an
error depending on what caller wants. oidvectortypes() needs to be
modified as well. I would suggest to change this interface as a
refactoring patch.
4) GetForeignServer and GetForeignDataWrapper need to have a
missing_ok. I suggest to refactor them as well with a separate patch.
5) For operators, there is format_operator_internal which has its own
idea of invalid objects. I think that the existing API should be
reworked.

So, while the work of this thread is largely possible and can be done
incrementally. The devil is in the details of how to handle the
existing APIs. Reaching an agreement about all the points above is key
to get a clean implementation we are happy with.
--
Michael

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

#8Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#7)
Re: Cache lookup errors with functions manipulation object addresses

On Fri, Jul 21, 2017 at 8:53 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

I can see your point. The v1 proposed above adds quite a lot of error
code churn to deal with the lack of missing_ok flag in
getObjectDescription, getObjectIdentity and getObjectIdentityParts.
And the cache lookup error messages cannot be object-specific either,
so I fell back to using %u/%u with the class as first identifier.
Let's go with what you suggest here then.

Thinking more on that, I'll go with the flag Alvaro suggested.

Before producing any v2, I would still need some sort of consensus
about a couple of points though to grab object details. Here is what I
think should be done:
1) For functions, let's remove format_procedure_qualified, and replace
it with format_procedure_extended which replaces
format_procedure_internal.

format_procedure_qualified is only used for objaddr.c, so I am going
here for not defining a compatibility set of macros.

2) For relation attributes, we have now get_relid_attribute_name()
which cannot tolerate failures, and get_attname which returns NULL on
errors. My suggestion here is to remove get_relid_attribute_name, and
add a missing_ok flag to get_attname. Let's do this as a refactoring
patch. One thing that may matter is modules calling the existing APIs.
We could provide a compatibility macro.

But here get_relid_attribute_name is old enough to have a
compatibility macro. So I'll add one in one of the refactoring
patches.

3) For types, the existing interface is more a mess. HEAD has
allow_invalid which is used by the SQL function format_type. My
suggestion here would be to remove allow_invalid and replace it with
missing_ok, to return NULL if the type has gone missing, or issue an
error depending on what caller wants. oidvectortypes() needs to be
modified as well. I would suggest to change this interface as a
refactoring patch.

With compatibility macros.

4) GetForeignServer and GetForeignDataWrapper need to have a
missing_ok. I suggest to refactor them as well with a separate patch.
5) For operators, there is format_operator_internal which has its own
idea of invalid objects. I think that the existing API should be
reworked.

No convinced much here, format_operator_qualified is not widely used
as far as I see, so I would just replace it with the _extended
version.

So, while the work of this thread is largely possible and can be done
incrementally. The devil is in the details of how to handle the
existing APIs. Reaching an agreement about all the points above is key
to get a clean implementation we are happy with.

Extra opinions always welcome.
--
Michael

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

#9Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#8)
4 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Aug 3, 2017 at 7:23 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Fri, Jul 21, 2017 at 8:53 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

I can see your point. The v1 proposed above adds quite a lot of error
code churn to deal with the lack of missing_ok flag in
getObjectDescription, getObjectIdentity and getObjectIdentityParts.
And the cache lookup error messages cannot be object-specific either,
so I fell back to using %u/%u with the class as first identifier.
Let's go with what you suggest here then.

Thinking more on that, I'll go with the flag Alvaro suggested.

This part is done. All the existing functions working in objectaddress
gain a missing_ok argument. The SQL-callable functions allow undefined
objects, and backend-side callers fail as before.

Before producing any v2, I would still need some sort of consensus
about a couple of points though to grab object details. Here is what I
think should be done:
1) For functions, let's remove format_procedure_qualified, and replace
it with format_procedure_extended which replaces
format_procedure_internal.

format_procedure_qualified is only used for objaddr.c, so I am going
here for not defining a compatibility set of macros.

While hacking on it again, I have again changed my mind to keep the
patch simple. On error, format_procedure and format_operator return
the operator numerically. The attached set of patches does not change
that.

2) For relation attributes, we have now get_relid_attribute_name()
which cannot tolerate failures, and get_attname which returns NULL on
errors. My suggestion here is to remove get_relid_attribute_name, and
add a missing_ok flag to get_attname. Let's do this as a refactoring
patch. One thing that may matter is modules calling the existing APIs.
We could provide a compatibility macro.

But here get_relid_attribute_name is old enough to have a
compatibility macro. So I'll add one in one of the refactoring
patches.

Here I have changed only get_attname signature and removed
get_relid_attribute_name() as any combination change would result in a
compilation failure.

3) For types, the existing interface is more a mess. HEAD has
allow_invalid which is used by the SQL function format_type. My
suggestion here would be to remove allow_invalid and replace it with
missing_ok, to return NULL if the type has gone missing, or issue an
error depending on what caller wants. oidvectortypes() needs to be
modified as well. I would suggest to change this interface as a
refactoring patch.

With compatibility macros.

Here as well, I have decided to keep the patch simple, and use the
existing flag allow_invalid as an equivalent for missing_ok. Similarly
to procedures and operators, we could always reinvent the wheel with
an extra set of routines... So extra opinions are welcome.

4) GetForeignServer and GetForeignDataWrapper need to have a
missing_ok. I suggest to refactor them as well with a separate patch.
5) For operators, there is format_operator_internal which has its own
idea of invalid objects. I think that the existing API should be
reworked.

No convinced much here, format_operator_qualified is not widely used
as far as I see, so I would just replace it with the _extended
version.

Here also I have finished with an unchanged interface as
format_operator_internal returns no errors.

So, while the work of this thread is largely possible and can be done
incrementally. The devil is in the details of how to handle the
existing APIs. Reaching an agreement about all the points above is key
to get a clean implementation we are happy with.

Extra opinions always welcome.

A set of patches easier to digest is attached:
- 0001 refactors things for attribute names.
- 0002 refactors FDW and foreign servers.
- 0003 refactors publications and subscriptions.
- 0004 is the main patch changing object address interface to avoid
cache lookup failures.
--
Michael

Attachments:

0001-Refactor-syscache-routines-to-get-attribute-name.patchapplication/octet-stream; name=0001-Refactor-syscache-routines-to-get-attribute-name.patchDownload
From dc5b7c6f3107822df18d863fadb0107d2ea9140d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 10:12:25 +0200
Subject: [PATCH 1/4] Refactor syscache routines to get attribute name

The existing routines are merged, and a missing_ok argument to return a
NULL result instead of an error is added to give more control to the
user. This unified interface will get used for object addresses.
---
 contrib/postgres_fdw/deparse.c      |  2 +-
 contrib/postgres_fdw/postgres_fdw.c |  2 +-
 contrib/sepgsql/dml.c               |  2 +-
 src/backend/catalog/heap.c          |  3 ++-
 src/backend/catalog/objectaddress.c | 10 ++++++----
 src/backend/parser/parse_relation.c |  2 +-
 src/backend/parser/parse_utilcmd.c  | 11 ++++++-----
 src/backend/utils/adt/ruleutils.c   | 28 ++++++++++++++++------------
 src/backend/utils/cache/lsyscache.c | 27 +++++++--------------------
 src/backend/utils/cache/relcache.c  |  2 +-
 src/include/utils/lsyscache.h       |  3 +--
 11 files changed, 43 insertions(+), 49 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 2af8364010..ec65dd8c22 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2026,7 +2026,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
 		 * FDW option, use attribute name.
 		 */
 		if (colname == NULL)
-			colname = get_relid_attribute_name(rte->relid, varattno);
+			colname = get_attname(rte->relid, varattno, false);
 
 		if (qualify_col)
 			ADD_REL_QUALIFIER(buf, varno);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index d77c2a70e4..e036704b6d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5127,7 +5127,7 @@ conversion_error_callback(void *arg)
 			if (var->varattno == 0)
 				is_wholerow = true;
 			else
-				attname = get_relid_attribute_name(rte->relid, var->varattno);
+				attname = get_attname(rte->relid, var->varattno, false);
 
 			relname = get_rel_name(rte->relid);
 		}
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index b643720e36..b9959b54d4 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -118,7 +118,7 @@ fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
 			continue;
 		}
 
-		attname = get_attname(parentId, attno);
+		attname = get_attname(parentId, attno, true);
 		if (!attname)
 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
 				 attno, parentId);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a376b99f1e..a348e586af 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2376,7 +2376,8 @@ AddRelationNewConstraints(Relation rel,
 
 			if (list_length(vars) == 1)
 				colname = get_attname(RelationGetRelid(rel),
-									  ((Var *) linitial(vars))->varattno);
+									  ((Var *) linitial(vars))->varattno,
+									  true);
 			else
 				colname = NULL;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6cac2dfd1d..ee1ccd0d12 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2664,8 +2664,9 @@ getObjectDescription(const ObjectAddress *object)
 			getRelationDescription(&buffer, object->objectId);
 			if (object->objectSubId != 0)
 				appendStringInfo(&buffer, _(" column %s"),
-								 get_relid_attribute_name(object->objectId,
-														  object->objectSubId));
+								 get_attname(object->objectId,
+											 object->objectSubId,
+											 false));
 			break;
 
 		case OCLASS_PROC:
@@ -4081,8 +4082,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *attr;
 
-				attr = get_relid_attribute_name(object->objectId,
-												object->objectSubId);
+				attr = get_attname(object->objectId,
+								   object->objectSubId,
+								   false);
 				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
 				if (objname)
 					*objname = lappend(*objname, attr);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 684a50d3df..9b54f0e5c2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2651,7 +2651,7 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+		return get_attname(rte->relid, attnum, false);
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 87cb4188a3..175c4510be 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1443,7 +1443,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
 			/* Simple index column */
 			char	   *attname;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			keycoltype = get_atttype(indrelid, attnum);
 
 			iparam->name = attname;
@@ -3319,8 +3319,9 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
-			colname = get_relid_attribute_name(RelationGetRelid(parent),
-											   key->partattrs[0]);
+			colname = get_attname(RelationGetRelid(parent),
+								  key->partattrs[0],
+								  false);
 		else
 			colname = deparse_expression((Node *) linitial(partexprs),
 										 deparse_context_for(RelationGetRelationName(parent),
@@ -3397,8 +3398,8 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 			/* Get the column's name in case we need to output an error */
 			if (key->partattrs[i] != 0)
-				colname = get_relid_attribute_name(RelationGetRelid(parent),
-												   key->partattrs[i]);
+				colname = get_attname(RelationGetRelid(parent),
+									  key->partattrs[i], false);
 			else
 			{
 				colname = deparse_expression((Node *) list_nth(partexprs, j),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index d83377d1d8..527c9c9364 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -907,8 +907,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 
 				if (i > 0)
 					appendStringInfoString(&buf, ", ");
-				attname = get_relid_attribute_name(trigrec->tgrelid,
-												   trigrec->tgattr.values[i]);
+				attname = get_attname(trigrec->tgrelid,
+									  trigrec->tgattr.values[i],
+									  false);
 				appendStringInfoString(&buf, quote_identifier(attname));
 			}
 		}
@@ -1289,7 +1290,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
 				appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(indrelid, attnum,
@@ -1532,7 +1533,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 		if (colno > 0)
 			appendStringInfoString(&buf, ", ");
 
-		attname = get_relid_attribute_name(statextrec->stxrelid, attnum);
+		attname = get_attname(statextrec->stxrelid, attnum, false);
 
 		appendStringInfoString(&buf, quote_identifier(attname));
 	}
@@ -1685,7 +1686,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(relid, attnum);
+			attname = get_attname(relid, attnum, false);
 			appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
@@ -2176,7 +2177,7 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 	{
 		char	   *colName;
 
-		colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
 			appendStringInfoString(buf, quote_identifier(colName));
@@ -5978,8 +5979,9 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -6282,8 +6284,9 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -10336,8 +10339,9 @@ processIndirection(Node *node, deparse_context *context)
 			 * target lists, but this function cannot be used for that case.
 			 */
 			Assert(list_length(fstore->fieldnums) == 1);
-			fieldname = get_relid_attribute_name(typrelid,
-												 linitial_int(fstore->fieldnums));
+			fieldname = get_attname(typrelid,
+									linitial_int(fstore->fieldnums),
+									false);
 			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
 
 			/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 82763f8013..7b32c2fc8f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -768,10 +768,11 @@ get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum)
  *		Given the relation id and the attribute number,
  *		return the "attname" field from the attribute relation.
  *
- * Note: returns a palloc'd copy of the string, or NULL if no such attribute.
+ * Note: returns a palloc'd copy of the string. In the event if an error,
+ * return NULL if missing_ok is true.
  */
 char *
-get_attname(Oid relid, AttrNumber attnum)
+get_attname(Oid relid, AttrNumber attnum, bool missing_ok)
 {
 	HeapTuple	tp;
 
@@ -787,26 +788,12 @@ get_attname(Oid relid, AttrNumber attnum)
 		ReleaseSysCache(tp);
 		return result;
 	}
-	else
-		return NULL;
-}
 
-/*
- * get_relid_attribute_name
- *
- * Same as above routine get_attname(), except that error
- * is handled by elog() instead of returning NULL.
- */
-char *
-get_relid_attribute_name(Oid relid, AttrNumber attnum)
-{
-	char	   *attname;
+	if (missing_ok)
+		return NULL;
 
-	attname = get_attname(relid, attnum);
-	if (attname == NULL)
-		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
-			 attnum, relid);
-	return attname;
+	elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+		 attnum, relid);
 }
 
 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 6bd93b03d0..12aa440e12 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5270,7 +5270,7 @@ errtablecol(Relation rel, int attnum)
 	if (attnum > 0 && attnum <= reldesc->natts)
 		colname = NameStr(reldesc->attrs[attnum - 1]->attname);
 	else
-		colname = get_relid_attribute_name(RelationGetRelid(rel), attnum);
+		colname = get_attname(RelationGetRelid(rel), attnum, false);
 
 	return errtablecolname(rel, colname);
 }
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 07208b56ce..0790f531a6 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -83,8 +83,7 @@ extern List *get_op_btree_interpretation(Oid opno);
 extern bool equality_ops_are_compatible(Oid opno1, Oid opno2);
 extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
 				  int16 procnum);
-extern char *get_attname(Oid relid, AttrNumber attnum);
-extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
+extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok);
 extern AttrNumber get_attnum(Oid relid, const char *attname);
 extern char get_attidentity(Oid relid, AttrNumber attnum);
 extern Oid	get_atttype(Oid relid, AttrNumber attnum);
-- 
2.13.4

0002-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchapplication/octet-stream; name=0002-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchDownload
From be6b61b5b6e57e4f96fd5e710b507fb911f1d2cf Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 10:44:12 +0200
Subject: [PATCH 2/4] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object.
---
 contrib/dblink/dblink.c             |  2 +-
 contrib/file_fdw/file_fdw.c         |  4 ++--
 contrib/postgres_fdw/connection.c   |  4 ++--
 contrib/postgres_fdw/postgres_fdw.c |  8 ++++----
 doc/src/sgml/fdwhandler.sgml        | 10 ++++++++--
 src/backend/catalog/objectaddress.c | 12 ++++++------
 src/backend/commands/foreigncmds.c  | 14 ++++++++------
 src/backend/commands/tablecmds.c    |  8 ++++----
 src/backend/foreign/foreign.c       | 24 ++++++++++++++++++++----
 src/include/foreign/foreign.h       |  4 ++--
 10 files changed, 57 insertions(+), 33 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 81136b131c..b5bf5c47d5 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2775,7 +2775,7 @@ get_connect_string(const char *servername)
 		Oid			userid = GetUserId();
 
 		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
+		fdw = GetForeignDataWrapper(fdwid, false);
 
 		/* Check permissions, user must have usage on the server. */
 		aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2396bd442f..1d18a17417 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -357,8 +357,8 @@ fileGetOptions(Oid foreigntableid,
 	 * Simplify?)
 	 */
 	table = GetForeignTable(foreigntableid);
-	server = GetForeignServer(table->serverid);
-	wrapper = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(table->serverid, false);
+	wrapper = GetForeignDataWrapper(server->fdwid, false);
 
 	options = NIL;
 	options = list_concat(options, wrapper->options);
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index be4ec07cf9..65f7806484 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -182,7 +182,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
 	 */
 	if (entry->conn == NULL)
 	{
-		ForeignServer *server = GetForeignServer(user->serverid);
+		ForeignServer *server = GetForeignServer(user->serverid, false);
 
 		/* Reset all transient state fields, to be sure all are clean */
 		entry->xact_depth = 0;
@@ -1003,7 +1003,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
 	umform = (Form_pg_user_mapping) GETSTRUCT(tup);
-	server = GetForeignServer(umform->umserver);
+	server = GetForeignServer(umform->umserver, false);
 	ReleaseSysCache(tup);
 
 	ereport(ERROR,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index e036704b6d..6800842575 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -506,7 +506,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/* Look up foreign-table catalog info. */
 	fpinfo->table = GetForeignTable(foreigntableid);
-	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+	fpinfo->server = GetForeignServer(fpinfo->table->serverid, false);
 
 	/*
 	 * Extract user-settable option values.  Note that per-table setting of
@@ -2038,7 +2038,7 @@ postgresIsForeignRelUpdatable(Relation rel)
 	updatable = true;
 
 	table = GetForeignTable(RelationGetRelid(rel));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 
 	foreach(lc, server->options)
 	{
@@ -3588,7 +3588,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	 * owner, even if the ANALYZE was started by some other user.
 	 */
 	table = GetForeignTable(RelationGetRelid(relation));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false);
 
@@ -3811,7 +3811,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	server = GetForeignServer(serverOid);
+	server = GetForeignServer(serverOid, false);
 	mapping = GetUserMapping(GetUserId(), server->serverid);
 	conn = GetConnection(mapping, false);
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index dbeaab555d..4e493d1e23 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1288,25 +1288,31 @@ ShutdownForeignScan(ForeignScanState *node);
     <para>
 <programlisting>
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid);
+GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignDataWrapper</structname>
      object for the foreign-data wrapper with the given OID.  A
      <structname>ForeignDataWrapper</structname> object contains properties
      of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</> is true, a <literal>NULL</> result is
+     returned to the caller instead of an error for an undefined
+     foreign-data wrapper.
     </para>
 
     <para>
 <programlisting>
 ForeignServer *
-GetForeignServer(Oid serverid);
+GetForeignServer(Oid serverid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignServer</structname> object
      for the foreign server with the given OID.  A
      <structname>ForeignServer</structname> object contains properties
      of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</> is true, a <literal>NULL</> result is
+     returned to the caller instead of an error for an undefined foreign
+     server.
     </para>
 
     <para>
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ee1ccd0d12..98fa02aa02 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3186,7 +3186,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
@@ -3195,7 +3195,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
@@ -3215,7 +3215,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
@@ -4690,7 +4690,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
 				if (objname)
 					*objname = list_make1(pstrdup(fdw->fdwname));
@@ -4701,7 +4701,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(srv->servername));
 				if (objname)
@@ -4724,7 +4724,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 9ad991507f..33f40ee98f 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -368,7 +368,8 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
 			if (aclresult != ACLCHECK_OK)
 			{
-				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw,
+																false);
 
 				aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
 			}
@@ -1033,7 +1034,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	if (stmt->options)
 	{
-		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw,
+														false);
 		Datum		datum;
 		bool		isnull;
 
@@ -1187,7 +1189,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 							stmt->servername)));
 	}
 
-	fdw = GetForeignDataWrapper(srv->fdwid);
+	fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_user_mapping.
@@ -1299,7 +1301,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 		 * Process the options.
 		 */
 
-		fdw = GetForeignDataWrapper(srv->fdwid);
+		fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
 								tp,
@@ -1479,7 +1481,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
 
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_foreign_table.
@@ -1542,7 +1544,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 	(void) LookupCreationNamespace(stmt->local_schema);
 
 	/* Get the FDW and check it supports IMPORT */
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 	if (!OidIsValid(fdw->fdwhandler))
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7859ef13ac..1ecf3ebc0c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9391,8 +9391,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(fttableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(fttableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	heap_close(ftrel, AccessShareLock);
 	ReleaseSysCache(tuple);
@@ -12249,8 +12249,8 @@ ATExecGenericOptions(Relation rel, List *options)
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(tableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(tableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	memset(repl_val, 0, sizeof(repl_val));
 	memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index a113bf540d..bc73300aa6 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -32,7 +32,7 @@
  * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
  */
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid)
+GetForeignDataWrapper(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +43,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -82,7 +86,11 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
 	if (!OidIsValid(fdwId))
 		return NULL;
 
-	return GetForeignDataWrapper(fdwId);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignDataWrapper(fdwId, false);
 }
 
 
@@ -90,7 +98,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  * GetForeignServer - look up the foreign server definition.
  */
 ForeignServer *
-GetForeignServer(Oid serverid)
+GetForeignServer(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +109,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
@@ -152,7 +164,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 	if (!OidIsValid(serverid))
 		return NULL;
 
-	return GetForeignServer(serverid);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignServer(serverid, false);
 }
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2f4c569d1d..13d5fab983 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -69,10 +69,10 @@ typedef struct ForeignTable
 } ForeignTable;
 
 
-extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServer(Oid serverid, bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
-extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.13.4

0003-Refactor-routines-for-subscription-and-publication-l.patchapplication/octet-stream; name=0003-Refactor-routines-for-subscription-and-publication-l.patchDownload
From e76a5f93280ca26dc92ea8230c48d894569850b3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 11:16:01 +0200
Subject: [PATCH 3/4] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  |  6 +++++-
 src/backend/catalog/pg_subscription.c |  6 +++++-
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 98fa02aa02..c4f993b8f7 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3367,7 +3367,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3384,7 +3385,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3395,7 +3396,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4890,7 +4891,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4911,7 +4912,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4930,7 +4931,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 3ef7ba8cd5..4f74c47e60 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -419,7 +419,7 @@ get_publication_oid(const char *pubname, bool missing_ok)
  * get_publication_name - given a publication Oid, look up the name
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -428,7 +428,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index fb53d71cd6..7c96859d0a 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -181,7 +181,7 @@ get_subscription_oid(const char *subname, bool missing_ok)
  * get_subscription_name - given a subscription OID, look up the name
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +190,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index aa148960cd..64a8cffef0 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -97,7 +97,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 274ff6bc42..7b8eb789c4 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -89,7 +89,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.13.4

0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchapplication/octet-stream; name=0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchDownload
From 00ed70bfffae4f7a3ed4d3b1bc46d6daf81b2a2e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 16:20:54 +0200
Subject: [PATCH 4/4] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 824 ++++++++++++++++++++-------
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/format_type.c          |  23 +-
 src/backend/utils/adt/regproc.c              |  16 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/builtins.h                 |   3 +
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 ++++
 21 files changed, 1541 insertions(+), 290 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 8fc5a87e00..0acb189571 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index b9959b54d4..1bf3208214 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -186,7 +186,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -263,7 +263,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index cbb9249be7..af569d24e4 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -372,7 +372,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -543,7 +543,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 14faa5fac6..c7bd656c62 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 228869a520..bb3ed26a00 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index d418577b75..6c2c12ef63 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 36319222e6..1f296bd52e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17490,7 +17490,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</> values for undefined objects.
   </para>
 
   <para>
@@ -17506,7 +17507,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</>;
    <parameter>identity</> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</> values.
   </para>
 
   <para>
@@ -17521,6 +17523,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6fffc290fa..4462cd88f9 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -607,11 +607,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -682,11 +682,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -777,12 +777,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -876,7 +876,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -899,7 +899,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -957,7 +958,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c4f993b8f7..2b82dc14f0 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -751,14 +751,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1626,7 +1632,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1657,7 +1663,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2649,10 +2655,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2661,22 +2669,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  false, missing_ok, false));
 			break;
 
 		case OCLASS_CAST:
@@ -2699,15 +2713,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2721,13 +2739,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2739,8 +2762,14 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2748,7 +2777,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2770,8 +2799,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2799,18 +2833,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2818,9 +2856,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2828,6 +2872,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2843,8 +2888,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2871,7 +2921,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2881,8 +2931,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2911,28 +2966,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2961,28 +3020,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3009,15 +3072,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3044,15 +3111,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3065,8 +3136,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3079,8 +3154,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
+				{
+					if (missing_ok)
+						break;
+					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3098,8 +3177,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3113,8 +3196,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3128,8 +3215,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3143,8 +3234,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3153,8 +3248,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3164,8 +3262,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3176,8 +3278,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3186,8 +3292,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3195,8 +3302,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3211,8 +3319,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -3250,8 +3363,16 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3308,8 +3429,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3321,8 +3446,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3349,15 +3478,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3366,9 +3500,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3381,8 +3517,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3395,8 +3535,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3408,8 +3550,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3427,6 +3573,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3442,14 +3592,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3459,7 +3609,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3519,7 +3673,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3529,7 +3683,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3573,7 +3731,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3588,6 +3750,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3661,8 +3825,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3685,8 +3855,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3703,6 +3879,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3730,23 +3907,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
+	else
+	{
+		pfree(identity);
 
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3760,7 +3951,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3770,11 +3961,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3790,7 +3983,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3927,6 +4121,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3934,7 +4132,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3942,7 +4141,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			Assert(buffer->len == 0);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -3988,7 +4194,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -3997,7 +4203,14 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
+	{
+		if (missing_ok)
+		{
+			heap_close(constrRel, AccessShareLock);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4015,7 +4228,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4023,7 +4237,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->proisagg)
@@ -4038,12 +4256,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4052,11 +4271,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4078,32 +4299,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId,
-								   object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1, false,
+											   missing_ok, true);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4121,8 +4351,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(castRel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4149,8 +4386,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4171,15 +4412,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4194,7 +4440,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4213,8 +4460,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4249,19 +4500,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4276,8 +4532,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for language %u",
 						 object->objectId);
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4294,10 +4554,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4311,8 +4572,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4338,7 +4603,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4347,8 +4613,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4380,13 +4650,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amopDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4434,13 +4713,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amprocDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4474,14 +4762,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(ruleDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4500,14 +4795,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(trigDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4521,8 +4823,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4539,8 +4845,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4562,8 +4872,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4585,8 +4899,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4608,8 +4926,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4631,8 +4953,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4649,7 +4975,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4663,8 +4991,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4678,8 +5010,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4691,10 +5027,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4702,11 +5041,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4721,8 +5063,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -4769,8 +5115,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4832,8 +5186,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4852,8 +5210,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4872,14 +5234,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(polDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4891,11 +5260,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4908,8 +5280,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4919,7 +5295,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4931,11 +5308,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4952,8 +5332,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(transformDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -4979,20 +5366,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5002,7 +5389,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5031,7 +5422,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5040,7 +5432,15 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			if (object)
+				*object = NIL;
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index dd6ca3e8f7..25f3c04b61 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 31b09a1da5..dbd3f2826c 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 938133bbe4..ba8a344476 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1428,10 +1428,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2089,8 +2090,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index e4340eed8c..0ee2b0c81b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1ecf3ebc0c..ea76cd4dbd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9093,7 +9093,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9141,7 +9141,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9160,7 +9160,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9178,7 +9178,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9239,7 +9239,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 00bfaca59e..08ab17470b 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -28,9 +28,6 @@
 
 #define MAX_INT32_LEN 11
 
-static char *format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify);
 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 
@@ -74,11 +71,11 @@ format_type(PG_FUNCTION_ARGS)
 	type_oid = PG_GETARG_OID(0);
 
 	if (PG_ARGISNULL(1))
-		result = format_type_internal(type_oid, -1, false, true, false);
+		result = format_type_extended(type_oid, -1, false, true, false);
 	else
 	{
 		typemod = PG_GETARG_INT32(1);
-		result = format_type_internal(type_oid, typemod, true, true, false);
+		result = format_type_extended(type_oid, typemod, true, true, false);
 	}
 
 	PG_RETURN_TEXT_P(cstring_to_text(result));
@@ -93,7 +90,7 @@ format_type(PG_FUNCTION_ARGS)
 char *
 format_type_be(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, false);
+	return format_type_extended(type_oid, -1, false, false, false);
 }
 
 /*
@@ -103,7 +100,7 @@ format_type_be(Oid type_oid)
 char *
 format_type_be_qualified(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, true);
+	return format_type_extended(type_oid, -1, false, false, true);
 }
 
 /*
@@ -112,7 +109,7 @@ format_type_be_qualified(Oid type_oid)
 char *
 format_type_with_typemod(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, false);
+	return format_type_extended(type_oid, typemod, true, false, false);
 }
 
 /*
@@ -122,14 +119,14 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 char *
 format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, true);
+	return format_type_extended(type_oid, typemod, true, false, true);
 }
 
 /*
  * Common workhorse.
  */
-static char *
-format_type_internal(Oid type_oid, int32 typemod,
+char *
+format_type_extended(Oid type_oid, int32 typemod,
 					 bool typemod_given, bool allow_invalid,
 					 bool force_qualify)
 {
@@ -141,7 +138,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 	char	   *buf;
 
 	if (type_oid == InvalidOid && allow_invalid)
-		return pstrdup("-");
+			return pstrdup("-");
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
@@ -437,7 +434,7 @@ oidvectortypes(PG_FUNCTION_ARGS)
 
 	for (num = 0; num < numargs; num++)
 	{
-		char	   *typename = format_type_internal(oidArray->values[num], -1,
+		char	   *typename = format_type_extended(oidArray->values[num], -1,
 													false, true, false);
 		size_t		slen = strlen(typename);
 
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 6fe81fab7e..bc73ab6f85 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for operator with OID %u",
 			 operator_oid);
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 5fc54d0e57..a8ab819d61 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 #endif							/* OBJECTADDRESS_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 762532f636..901a60f172 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -116,6 +116,9 @@ extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
+extern char *format_type_extended(Oid type_oid, int32 typemod,
+								  bool typemod_given, bool allow_invalid,
+								  bool force_qualify);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
 /* quote.c */
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index ba46bd7d58..ed7d579242 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 1fdadbc9ef..92e9f082bc 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -483,3 +483,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 12 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 63821b8008..e45af4bb08 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -211,3 +211,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.13.4

#10Aleksander Alekseev
a.alekseev@postgrespro.ru
In reply to: Michael Paquier (#9)
Re: Cache lookup errors with functions manipulation object addresses

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: tested, passed
Documentation: tested, passed

I believe this patch is "Ready for Committer".

The new status of this patch is: Ready for Committer

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

#11Michael Paquier
michael.paquier@gmail.com
In reply to: Aleksander Alekseev (#10)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, Aug 9, 2017 at 2:47 PM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:

I believe this patch is "Ready for Committer".

The new status of this patch is: Ready for Committer

Thanks for the lookup, but I think that this is still hasty as no
discussion has happened about a couple of APIs to get object names.
Types, operators and functions have no "cache lookup" and prefer
producing names like "???" or "-". We may want to change that. Or not.
The current patch keeps existing interfaces as much as possible but
those existing caveats remain.
--
Michael

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

#12Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#11)
4 attachment(s)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Thu, Aug 10, 2017 at 9:48 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Wed, Aug 9, 2017 at 2:47 PM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:

I believe this patch is "Ready for Committer".

The new status of this patch is: Ready for Committer

Thanks for the lookup, but I think that this is still hasty as no
discussion has happened about a couple of APIs to get object names.
Types, operators and functions have no "cache lookup" and prefer
producing names like "???" or "-". We may want to change that. Or not.
The current patch keeps existing interfaces as much as possible but
those existing caveats remain.

Attached is a rebased patch set. Álvaro, as you have introduced most
of the problems with 4464303 & friends dated of 2015 when you
introduced get_object_address(), could you look at this patch set?
--
Michael

Attachments:

0001-Refactor-syscache-routines-to-get-attribute-name.patchapplication/octet-stream; name=0001-Refactor-syscache-routines-to-get-attribute-name.patchDownload
From 110ccd320c534bb3cca657705bc3a8c5075ed950 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 10:12:25 +0200
Subject: [PATCH 1/4] Refactor syscache routines to get attribute name

The existing routines are merged, and a missing_ok argument to return a
NULL result instead of an error is added to give more control to the
user. This unified interface will get used for object addresses.
---
 contrib/postgres_fdw/deparse.c      |  2 +-
 contrib/postgres_fdw/postgres_fdw.c |  2 +-
 contrib/sepgsql/dml.c               |  2 +-
 src/backend/catalog/heap.c          |  3 ++-
 src/backend/catalog/objectaddress.c | 10 ++++++----
 src/backend/parser/parse_relation.c |  2 +-
 src/backend/parser/parse_utilcmd.c  | 11 ++++++-----
 src/backend/utils/adt/ruleutils.c   | 28 ++++++++++++++++------------
 src/backend/utils/cache/lsyscache.c | 27 +++++++--------------------
 src/backend/utils/cache/relcache.c  |  2 +-
 src/include/utils/lsyscache.h       |  3 +--
 11 files changed, 43 insertions(+), 49 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 0876589fe5..e5756d37e6 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2026,7 +2026,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
 		 * FDW option, use attribute name.
 		 */
 		if (colname == NULL)
-			colname = get_relid_attribute_name(rte->relid, varattno);
+			colname = get_attname(rte->relid, varattno, false);
 
 		if (qualify_col)
 			ADD_REL_QUALIFIER(buf, varno);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index fb65e2eb20..839cab691e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5128,7 +5128,7 @@ conversion_error_callback(void *arg)
 			if (var->varattno == 0)
 				is_wholerow = true;
 			else
-				attname = get_relid_attribute_name(rte->relid, var->varattno);
+				attname = get_attname(rte->relid, var->varattno, false);
 
 			relname = get_rel_name(rte->relid);
 		}
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index b643720e36..b9959b54d4 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -118,7 +118,7 @@ fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
 			continue;
 		}
 
-		attname = get_attname(parentId, attno);
+		attname = get_attname(parentId, attno, true);
 		if (!attname)
 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
 				 attno, parentId);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9e14880b99..76b037e8a7 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2402,7 +2402,8 @@ AddRelationNewConstraints(Relation rel,
 
 			if (list_length(vars) == 1)
 				colname = get_attname(RelationGetRelid(rel),
-									  ((Var *) linitial(vars))->varattno);
+									  ((Var *) linitial(vars))->varattno,
+									  true);
 			else
 				colname = NULL;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8d55c76fc4..564eee0cdf 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2664,8 +2664,9 @@ getObjectDescription(const ObjectAddress *object)
 			getRelationDescription(&buffer, object->objectId);
 			if (object->objectSubId != 0)
 				appendStringInfo(&buffer, _(" column %s"),
-								 get_relid_attribute_name(object->objectId,
-														  object->objectSubId));
+								 get_attname(object->objectId,
+											 object->objectSubId,
+											 false));
 			break;
 
 		case OCLASS_PROC:
@@ -4081,8 +4082,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *attr;
 
-				attr = get_relid_attribute_name(object->objectId,
-												object->objectSubId);
+				attr = get_attname(object->objectId,
+								   object->objectSubId,
+								   false);
 				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
 				if (objname)
 					*objname = lappend(*objname, attr);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 58bdb23c4e..8118581778 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2687,7 +2687,7 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+		return get_attname(rte->relid, attnum, false);
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 8461da490a..5dca65eb98 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1446,7 +1446,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
 			/* Simple index column */
 			char	   *attname;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			keycoltype = get_atttype(indrelid, attnum);
 
 			iparam->name = attname;
@@ -3360,8 +3360,9 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
-			colname = get_relid_attribute_name(RelationGetRelid(parent),
-											   key->partattrs[0]);
+			colname = get_attname(RelationGetRelid(parent),
+								  key->partattrs[0],
+								  false);
 		else
 			colname = deparse_expression((Node *) linitial(partexprs),
 										 deparse_context_for(RelationGetRelationName(parent),
@@ -3445,8 +3446,8 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 			/* Get the column's name in case we need to output an error */
 			if (key->partattrs[i] != 0)
-				colname = get_relid_attribute_name(RelationGetRelid(parent),
-												   key->partattrs[i]);
+				colname = get_attname(RelationGetRelid(parent),
+									  key->partattrs[i], false);
 			else
 			{
 				colname = deparse_expression((Node *) list_nth(partexprs, j),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 06cf32f5d7..3001adfa54 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -908,8 +908,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 
 				if (i > 0)
 					appendStringInfoString(&buf, ", ");
-				attname = get_relid_attribute_name(trigrec->tgrelid,
-												   trigrec->tgattr.values[i]);
+				attname = get_attname(trigrec->tgrelid,
+									  trigrec->tgattr.values[i],
+									  false);
 				appendStringInfoString(&buf, quote_identifier(attname));
 			}
 		}
@@ -1290,7 +1291,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
 				appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(indrelid, attnum,
@@ -1533,7 +1534,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 		if (colno > 0)
 			appendStringInfoString(&buf, ", ");
 
-		attname = get_relid_attribute_name(statextrec->stxrelid, attnum);
+		attname = get_attname(statextrec->stxrelid, attnum, false);
 
 		appendStringInfoString(&buf, quote_identifier(attname));
 	}
@@ -1690,7 +1691,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(relid, attnum);
+			attname = get_attname(relid, attnum, false);
 			appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
@@ -2193,7 +2194,7 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 	{
 		char	   *colName;
 
-		colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
 			appendStringInfoString(buf, quote_identifier(colName));
@@ -5998,8 +5999,9 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -6302,8 +6304,9 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -10323,8 +10326,9 @@ processIndirection(Node *node, deparse_context *context)
 			 * target lists, but this function cannot be used for that case.
 			 */
 			Assert(list_length(fstore->fieldnums) == 1);
-			fieldname = get_relid_attribute_name(typrelid,
-												 linitial_int(fstore->fieldnums));
+			fieldname = get_attname(typrelid,
+									linitial_int(fstore->fieldnums),
+									false);
 			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
 
 			/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 0ea2f2bc54..cb14869a8b 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -768,10 +768,11 @@ get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum)
  *		Given the relation id and the attribute number,
  *		return the "attname" field from the attribute relation.
  *
- * Note: returns a palloc'd copy of the string, or NULL if no such attribute.
+ * Note: returns a palloc'd copy of the string. In the event if an error,
+ * return NULL if missing_ok is true.
  */
 char *
-get_attname(Oid relid, AttrNumber attnum)
+get_attname(Oid relid, AttrNumber attnum, bool missing_ok)
 {
 	HeapTuple	tp;
 
@@ -787,26 +788,12 @@ get_attname(Oid relid, AttrNumber attnum)
 		ReleaseSysCache(tp);
 		return result;
 	}
-	else
-		return NULL;
-}
 
-/*
- * get_relid_attribute_name
- *
- * Same as above routine get_attname(), except that error
- * is handled by elog() instead of returning NULL.
- */
-char *
-get_relid_attribute_name(Oid relid, AttrNumber attnum)
-{
-	char	   *attname;
+	if (missing_ok)
+		return NULL;
 
-	attname = get_attname(relid, attnum);
-	if (attname == NULL)
-		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
-			 attnum, relid);
-	return attname;
+	elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+		 attnum, relid);
 }
 
 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 1908420d82..25c9c04f05 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5281,7 +5281,7 @@ errtablecol(Relation rel, int attnum)
 	if (attnum > 0 && attnum <= reldesc->natts)
 		colname = NameStr(TupleDescAttr(reldesc, attnum - 1)->attname);
 	else
-		colname = get_relid_attribute_name(RelationGetRelid(rel), attnum);
+		colname = get_attname(RelationGetRelid(rel), attnum, false);
 
 	return errtablecolname(rel, colname);
 }
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 07208b56ce..0790f531a6 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -83,8 +83,7 @@ extern List *get_op_btree_interpretation(Oid opno);
 extern bool equality_ops_are_compatible(Oid opno1, Oid opno2);
 extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
 				  int16 procnum);
-extern char *get_attname(Oid relid, AttrNumber attnum);
-extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
+extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok);
 extern AttrNumber get_attnum(Oid relid, const char *attname);
 extern char get_attidentity(Oid relid, AttrNumber attnum);
 extern Oid	get_atttype(Oid relid, AttrNumber attnum);
-- 
2.15.0

0002-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchapplication/octet-stream; name=0002-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchDownload
From be57069a20882111eb01a3c34f76abae27737dd9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 10:44:12 +0200
Subject: [PATCH 2/4] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object.
---
 contrib/dblink/dblink.c             |  2 +-
 contrib/file_fdw/file_fdw.c         |  4 ++--
 contrib/postgres_fdw/connection.c   |  4 ++--
 contrib/postgres_fdw/postgres_fdw.c |  8 ++++----
 doc/src/sgml/fdwhandler.sgml        | 10 ++++++++--
 src/backend/catalog/objectaddress.c | 12 ++++++------
 src/backend/commands/foreigncmds.c  | 14 ++++++++------
 src/backend/commands/tablecmds.c    |  8 ++++----
 src/backend/foreign/foreign.c       | 24 ++++++++++++++++++++----
 src/include/foreign/foreign.h       |  4 ++--
 10 files changed, 57 insertions(+), 33 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index a7e22b378a..d5a6a9f0ba 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2784,7 +2784,7 @@ get_connect_string(const char *servername)
 		Oid			userid = GetUserId();
 
 		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
+		fdw = GetForeignDataWrapper(fdwid, false);
 
 		/* Check permissions, user must have usage on the server. */
 		aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 370cc365d6..f8f78a56e8 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -357,8 +357,8 @@ fileGetOptions(Oid foreigntableid,
 	 * Simplify?)
 	 */
 	table = GetForeignTable(foreigntableid);
-	server = GetForeignServer(table->serverid);
-	wrapper = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(table->serverid, false);
+	wrapper = GetForeignDataWrapper(server->fdwid, false);
 
 	options = NIL;
 	options = list_concat(options, wrapper->options);
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index be4ec07cf9..65f7806484 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -182,7 +182,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
 	 */
 	if (entry->conn == NULL)
 	{
-		ForeignServer *server = GetForeignServer(user->serverid);
+		ForeignServer *server = GetForeignServer(user->serverid, false);
 
 		/* Reset all transient state fields, to be sure all are clean */
 		entry->xact_depth = 0;
@@ -1003,7 +1003,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
 	umform = (Form_pg_user_mapping) GETSTRUCT(tup);
-	server = GetForeignServer(umform->umserver);
+	server = GetForeignServer(umform->umserver, false);
 	ReleaseSysCache(tup);
 
 	ereport(ERROR,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 839cab691e..94ba92c0b8 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -506,7 +506,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/* Look up foreign-table catalog info. */
 	fpinfo->table = GetForeignTable(foreigntableid);
-	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+	fpinfo->server = GetForeignServer(fpinfo->table->serverid, false);
 
 	/*
 	 * Extract user-settable option values.  Note that per-table setting of
@@ -2039,7 +2039,7 @@ postgresIsForeignRelUpdatable(Relation rel)
 	updatable = true;
 
 	table = GetForeignTable(RelationGetRelid(rel));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 
 	foreach(lc, server->options)
 	{
@@ -3589,7 +3589,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	 * owner, even if the ANALYZE was started by some other user.
 	 */
 	table = GetForeignTable(RelationGetRelid(relation));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false);
 
@@ -3812,7 +3812,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	server = GetForeignServer(serverOid);
+	server = GetForeignServer(serverOid, false);
 	mapping = GetUserMapping(GetUserId(), server->serverid);
 	conn = GetConnection(mapping, false);
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index a2f8137713..64ad6178db 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1330,25 +1330,31 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid);
+GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignDataWrapper</structname>
      object for the foreign-data wrapper with the given OID.  A
      <structname>ForeignDataWrapper</structname> object contains properties
      of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</> is true, a <literal>NULL</> result is
+     returned to the caller instead of an error for an undefined
+     foreign-data wrapper.
     </para>
 
     <para>
 <programlisting>
 ForeignServer *
-GetForeignServer(Oid serverid);
+GetForeignServer(Oid serverid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignServer</structname> object
      for the foreign server with the given OID.  A
      <structname>ForeignServer</structname> object contains properties
      of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</> is true, a <literal>NULL</> result is
+     returned to the caller instead of an error for an undefined foreign
+     server.
     </para>
 
     <para>
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 564eee0cdf..0720e71b04 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3186,7 +3186,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
@@ -3195,7 +3195,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
@@ -3215,7 +3215,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
@@ -4690,7 +4690,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
 				if (objname)
 					*objname = list_make1(pstrdup(fdw->fdwname));
@@ -4701,7 +4701,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(srv->servername));
 				if (objname)
@@ -4724,7 +4724,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 9ad991507f..33f40ee98f 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -368,7 +368,8 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
 			if (aclresult != ACLCHECK_OK)
 			{
-				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw,
+																false);
 
 				aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
 			}
@@ -1033,7 +1034,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	if (stmt->options)
 	{
-		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw,
+														false);
 		Datum		datum;
 		bool		isnull;
 
@@ -1187,7 +1189,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 							stmt->servername)));
 	}
 
-	fdw = GetForeignDataWrapper(srv->fdwid);
+	fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_user_mapping.
@@ -1299,7 +1301,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 		 * Process the options.
 		 */
 
-		fdw = GetForeignDataWrapper(srv->fdwid);
+		fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
 								tp,
@@ -1479,7 +1481,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
 
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_foreign_table.
@@ -1542,7 +1544,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 	(void) LookupCreationNamespace(stmt->local_schema);
 
 	/* Get the FDW and check it supports IMPORT */
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 	if (!OidIsValid(fdw->fdwhandler))
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d19846d005..3971532447 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9519,8 +9519,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(fttableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(fttableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	heap_close(ftrel, AccessShareLock);
 	ReleaseSysCache(tuple);
@@ -12435,8 +12435,8 @@ ATExecGenericOptions(Relation rel, List *options)
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(tableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(tableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	memset(repl_val, 0, sizeof(repl_val));
 	memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 45fca52621..cad1c41801 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -32,7 +32,7 @@
  * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
  */
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid)
+GetForeignDataWrapper(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +43,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -82,7 +86,11 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
 	if (!OidIsValid(fdwId))
 		return NULL;
 
-	return GetForeignDataWrapper(fdwId);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignDataWrapper(fdwId, false);
 }
 
 
@@ -90,7 +98,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  * GetForeignServer - look up the foreign server definition.
  */
 ForeignServer *
-GetForeignServer(Oid serverid)
+GetForeignServer(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +109,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
@@ -152,7 +164,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 	if (!OidIsValid(serverid))
 		return NULL;
 
-	return GetForeignServer(serverid);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignServer(serverid, false);
 }
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 2f4c569d1d..13d5fab983 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -69,10 +69,10 @@ typedef struct ForeignTable
 } ForeignTable;
 
 
-extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServer(Oid serverid, bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
-extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.15.0

0003-Refactor-routines-for-subscription-and-publication-l.patchapplication/octet-stream; name=0003-Refactor-routines-for-subscription-and-publication-l.patchDownload
From 192dba88a5847c285b96efe66a214d2e64ecdd27 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 11:16:01 +0200
Subject: [PATCH 3/4] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  |  6 +++++-
 src/backend/catalog/pg_subscription.c |  6 +++++-
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 0720e71b04..6ce8f58fbe 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3367,7 +3367,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3384,7 +3385,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3395,7 +3396,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4890,7 +4891,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4911,7 +4912,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4930,7 +4931,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 3ef7ba8cd5..4f74c47e60 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -419,7 +419,7 @@ get_publication_oid(const char *pubname, bool missing_ok)
  * get_publication_name - given a publication Oid, look up the name
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -428,7 +428,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index fb53d71cd6..7c96859d0a 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -181,7 +181,7 @@ get_subscription_oid(const char *subname, bool missing_ok)
  * get_subscription_name - given a subscription OID, look up the name
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +190,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index aa148960cd..64a8cffef0 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -97,7 +97,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 274ff6bc42..7b8eb789c4 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -89,7 +89,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.15.0

0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchapplication/octet-stream; name=0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchDownload
From a0faf5691823f50f0be0d76550a9458618afaa65 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 16:20:54 +0200
Subject: [PATCH 4/4] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 824 ++++++++++++++++++++-------
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/format_type.c          |  23 +-
 src/backend/utils/adt/regproc.c              |  16 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/builtins.h                 |   3 +
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 ++++
 21 files changed, 1541 insertions(+), 290 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 8fc5a87e00..0acb189571 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index b9959b54d4..1bf3208214 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -186,7 +186,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -263,7 +263,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index cbb9249be7..af569d24e4 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -372,7 +372,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -543,7 +543,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 14faa5fac6..c7bd656c62 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 228869a520..bb3ed26a00 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index d418577b75..6c2c12ef63 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4dd9d029e6..c635f3b522 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17538,7 +17538,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</> values for undefined objects.
   </para>
 
   <para>
@@ -17554,7 +17555,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</> values.
   </para>
 
   <para>
@@ -17569,6 +17571,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 033c4358ea..ccefc8b06d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -607,11 +607,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -682,11 +682,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -777,12 +777,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -876,7 +876,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -899,7 +899,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -957,7 +958,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 6ce8f58fbe..38bb52f93f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -751,14 +751,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1626,7 +1632,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1657,7 +1663,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2649,10 +2655,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2661,22 +2669,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  false, missing_ok, false));
 			break;
 
 		case OCLASS_CAST:
@@ -2699,15 +2713,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2721,13 +2739,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2739,8 +2762,14 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2748,7 +2777,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2770,8 +2799,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2799,18 +2833,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2818,9 +2856,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2828,6 +2872,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2843,8 +2888,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2871,7 +2921,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2881,8 +2931,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2911,28 +2966,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2961,28 +3020,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3009,15 +3072,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3044,15 +3111,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3065,8 +3136,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3079,8 +3154,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
+				{
+					if (missing_ok)
+						break;
+					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3098,8 +3177,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3113,8 +3196,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3128,8 +3215,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3143,8 +3234,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3153,8 +3248,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3164,8 +3262,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3176,8 +3278,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3186,8 +3292,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3195,8 +3302,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3211,8 +3319,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -3250,8 +3363,16 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3308,8 +3429,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3321,8 +3446,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3349,15 +3478,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3366,9 +3500,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3381,8 +3517,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3395,8 +3535,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3408,8 +3550,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3427,6 +3573,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3442,14 +3592,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3459,7 +3609,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3519,7 +3673,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3529,7 +3683,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3573,7 +3731,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3588,6 +3750,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3661,8 +3825,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3685,8 +3855,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3703,6 +3879,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3730,23 +3907,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
+	else
+	{
+		pfree(identity);
 
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3760,7 +3951,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3770,11 +3961,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3790,7 +3983,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3927,6 +4121,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3934,7 +4132,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3942,7 +4141,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			Assert(buffer->len == 0);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -3988,7 +4194,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -3997,7 +4203,14 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
+	{
+		if (missing_ok)
+		{
+			heap_close(constrRel, AccessShareLock);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4015,7 +4228,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4023,7 +4237,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->proisagg)
@@ -4038,12 +4256,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4052,11 +4271,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4078,32 +4299,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId,
-								   object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1, false,
+											   missing_ok, true);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4121,8 +4351,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(castRel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4149,8 +4386,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4171,15 +4412,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4194,7 +4440,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4213,8 +4460,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4249,19 +4500,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4276,8 +4532,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for language %u",
 						 object->objectId);
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4294,10 +4554,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4311,8 +4572,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4338,7 +4603,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4347,8 +4613,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4380,13 +4650,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amopDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4434,13 +4713,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amprocDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4474,14 +4762,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(ruleDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4500,14 +4795,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(trigDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4521,8 +4823,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4539,8 +4845,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4562,8 +4872,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4585,8 +4899,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4608,8 +4926,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4631,8 +4953,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4649,7 +4975,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4663,8 +4991,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4678,8 +5010,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4691,10 +5027,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4702,11 +5041,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4721,8 +5063,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -4769,8 +5115,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4832,8 +5186,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4852,8 +5210,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4872,14 +5234,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(polDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4891,11 +5260,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4908,8 +5280,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4919,7 +5295,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4931,11 +5308,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4952,8 +5332,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(transformDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -4979,20 +5366,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5002,7 +5389,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5031,7 +5422,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5040,7 +5432,15 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			if (object)
+				*object = NIL;
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index cf0086b9bd..56d429d8dc 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 31b09a1da5..dbd3f2826c 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index fa7d0d015a..08e3035c14 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1428,10 +1428,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2089,8 +2090,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 9f77d25352..20f2c953c1 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3971532447..896a8771d6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9221,7 +9221,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9269,7 +9269,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9288,7 +9288,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9306,7 +9306,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9367,7 +9367,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 00bfaca59e..08ab17470b 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -28,9 +28,6 @@
 
 #define MAX_INT32_LEN 11
 
-static char *format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify);
 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 
@@ -74,11 +71,11 @@ format_type(PG_FUNCTION_ARGS)
 	type_oid = PG_GETARG_OID(0);
 
 	if (PG_ARGISNULL(1))
-		result = format_type_internal(type_oid, -1, false, true, false);
+		result = format_type_extended(type_oid, -1, false, true, false);
 	else
 	{
 		typemod = PG_GETARG_INT32(1);
-		result = format_type_internal(type_oid, typemod, true, true, false);
+		result = format_type_extended(type_oid, typemod, true, true, false);
 	}
 
 	PG_RETURN_TEXT_P(cstring_to_text(result));
@@ -93,7 +90,7 @@ format_type(PG_FUNCTION_ARGS)
 char *
 format_type_be(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, false);
+	return format_type_extended(type_oid, -1, false, false, false);
 }
 
 /*
@@ -103,7 +100,7 @@ format_type_be(Oid type_oid)
 char *
 format_type_be_qualified(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, true);
+	return format_type_extended(type_oid, -1, false, false, true);
 }
 
 /*
@@ -112,7 +109,7 @@ format_type_be_qualified(Oid type_oid)
 char *
 format_type_with_typemod(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, false);
+	return format_type_extended(type_oid, typemod, true, false, false);
 }
 
 /*
@@ -122,14 +119,14 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 char *
 format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, true);
+	return format_type_extended(type_oid, typemod, true, false, true);
 }
 
 /*
  * Common workhorse.
  */
-static char *
-format_type_internal(Oid type_oid, int32 typemod,
+char *
+format_type_extended(Oid type_oid, int32 typemod,
 					 bool typemod_given, bool allow_invalid,
 					 bool force_qualify)
 {
@@ -141,7 +138,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 	char	   *buf;
 
 	if (type_oid == InvalidOid && allow_invalid)
-		return pstrdup("-");
+			return pstrdup("-");
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
@@ -437,7 +434,7 @@ oidvectortypes(PG_FUNCTION_ARGS)
 
 	for (num = 0; num < numargs; num++)
 	{
-		char	   *typename = format_type_internal(oidArray->values[num], -1,
+		char	   *typename = format_type_extended(oidArray->values[num], -1,
 													false, true, false);
 		size_t		slen = strlen(typename);
 
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index afd0c00b8a..2310a31b09 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for operator with OID %u",
 			 operator_oid);
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 5fc54d0e57..a8ab819d61 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 #endif							/* OBJECTADDRESS_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 762532f636..901a60f172 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -116,6 +116,9 @@ extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
+extern char *format_type_extended(Oid type_oid, int32 typemod,
+								  bool typemod_given, bool allow_invalid,
+								  bool force_qualify);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
 /* quote.c */
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index ba46bd7d58..ed7d579242 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 1fdadbc9ef..92e9f082bc 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -483,3 +483,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 12 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 63821b8008..e45af4bb08 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -211,3 +211,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.15.0

#13Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#12)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Fri, Nov 24, 2017 at 9:13 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Attached is a rebased patch set. Álvaro, as you have introduced most
of the problems with 4464303 & friends dated of 2015 when you
introduced get_object_address(), could you look at this patch set?

Moved to next commit fest.
--
Michael

#14Thomas Munro
thomas.munro@enterprisedb.com
In reply to: Michael Paquier (#13)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Mon, Nov 27, 2017 at 1:01 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Fri, Nov 24, 2017 at 9:13 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Attached is a rebased patch set. Álvaro, as you have introduced most
of the problems with 4464303 & friends dated of 2015 when you
introduced get_object_address(), could you look at this patch set?

Hi Michael,

FYI:

func.sgml:17550: parser error : Opening and ending tag mismatch:
literal line 17550 and unparseable
<literal>NULL</> values for undefined objects.
^
func.sgml:17567: parser error : Opening and ending tag mismatch:
literal line 17567 and unparseable
with <literal>NULL</> values.
^
func.sgml:17582: parser error : Opening and ending tag mismatch:
literal line 17582 and unparseable
Undefined objects are identified with <literal>NULL</> values.
^
func.sgml:20721: parser error : chunk is not well balanced
postgres.sgml:105: parser error : Failure to process entity func
&func;
^
postgres.sgml:105: parser error : Entity 'func' not defined
&func;
^

--
Thomas Munro
http://www.enterprisedb.com

#15Michael Paquier
michael.paquier@gmail.com
In reply to: Thomas Munro (#14)
4 attachment(s)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Fri, Jan 12, 2018 at 01:45:19PM +1300, Thomas Munro wrote:

FYI:

func.sgml:17550: parser error : Opening and ending tag mismatch:
literal line 17550 and unparseable
<literal>NULL</> values for undefined objects.
^
func.sgml:17567: parser error : Opening and ending tag mismatch:
literal line 17567 and unparseable
with <literal>NULL</> values.
^
func.sgml:17582: parser error : Opening and ending tag mismatch:
literal line 17582 and unparseable
Undefined objects are identified with <literal>NULL</> values.
^
func.sgml:20721: parser error : chunk is not well balanced
postgres.sgml:105: parser error : Failure to process entity func
&func;
^
postgres.sgml:105: parser error : Entity 'func' not defined
&func;
^

Thanks Mr. Robot and Thomas for the reminder. Attached is an updated
patch set taking care of those warnings, 0002 and 0004 being impacted.
--
Michael

Attachments:

0001-Refactor-syscache-routines-to-get-attribute-name.patchtext/plain; charset=us-asciiDownload
From a2536f8ae002bde4338fcf4fa04c248c5630475d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 10:12:25 +0200
Subject: [PATCH 1/4] Refactor syscache routines to get attribute name

The existing routines are merged, and a missing_ok argument to return a
NULL result instead of an error is added to give more control to the
user. This unified interface will get used for object addresses.
---
 contrib/postgres_fdw/deparse.c      |  2 +-
 contrib/postgres_fdw/postgres_fdw.c |  2 +-
 contrib/sepgsql/dml.c               |  2 +-
 src/backend/catalog/heap.c          |  3 ++-
 src/backend/catalog/objectaddress.c | 10 ++++++----
 src/backend/parser/parse_relation.c |  2 +-
 src/backend/parser/parse_utilcmd.c  | 11 ++++++-----
 src/backend/utils/adt/ruleutils.c   | 28 ++++++++++++++++------------
 src/backend/utils/cache/lsyscache.c | 27 +++++++--------------------
 src/backend/utils/cache/relcache.c  |  2 +-
 src/include/utils/lsyscache.h       |  3 +--
 11 files changed, 43 insertions(+), 49 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 96f804a28d..b1bce61f2c 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2026,7 +2026,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
 		 * FDW option, use attribute name.
 		 */
 		if (colname == NULL)
-			colname = get_relid_attribute_name(rte->relid, varattno);
+			colname = get_attname(rte->relid, varattno, false);
 
 		if (qualify_col)
 			ADD_REL_QUALIFIER(buf, varno);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index c6e1211f8f..469e83a023 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5128,7 +5128,7 @@ conversion_error_callback(void *arg)
 			if (var->varattno == 0)
 				is_wholerow = true;
 			else
-				attname = get_relid_attribute_name(rte->relid, var->varattno);
+				attname = get_attname(rte->relid, var->varattno, false);
 
 			relname = get_rel_name(rte->relid);
 		}
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 36cdb27a76..10f65a071e 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -118,7 +118,7 @@ fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
 			continue;
 		}
 
-		attname = get_attname(parentId, attno);
+		attname = get_attname(parentId, attno, true);
 		if (!attname)
 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
 				 attno, parentId);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 089b7965f2..0896079934 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2404,7 +2404,8 @@ AddRelationNewConstraints(Relation rel,
 
 			if (list_length(vars) == 1)
 				colname = get_attname(RelationGetRelid(rel),
-									  ((Var *) linitial(vars))->varattno);
+									  ((Var *) linitial(vars))->varattno,
+									  true);
 			else
 				colname = NULL;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index bc999ca3c4..29147feb6b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2671,8 +2671,9 @@ getObjectDescription(const ObjectAddress *object)
 			getRelationDescription(&buffer, object->objectId);
 			if (object->objectSubId != 0)
 				appendStringInfo(&buffer, _(" column %s"),
-								 get_relid_attribute_name(object->objectId,
-														  object->objectSubId));
+								 get_attname(object->objectId,
+											 object->objectSubId,
+											 false));
 			break;
 
 		case OCLASS_PROC:
@@ -4090,8 +4091,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *attr;
 
-				attr = get_relid_attribute_name(object->objectId,
-												object->objectSubId);
+				attr = get_attname(object->objectId,
+								   object->objectSubId,
+								   false);
 				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
 				if (objname)
 					*objname = lappend(*objname, attr);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 2625da5327..053ae02c9f 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2687,7 +2687,7 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+		return get_attname(rte->relid, attnum, false);
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 128f1679c6..cd505a76f4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1458,7 +1458,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
 			/* Simple index column */
 			char	   *attname;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			keycoltype = get_atttype(indrelid, attnum);
 
 			iparam->name = attname;
@@ -3373,8 +3373,9 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
-			colname = get_relid_attribute_name(RelationGetRelid(parent),
-											   key->partattrs[0]);
+			colname = get_attname(RelationGetRelid(parent),
+								  key->partattrs[0],
+								  false);
 		else
 			colname = deparse_expression((Node *) linitial(partexprs),
 										 deparse_context_for(RelationGetRelationName(parent),
@@ -3458,8 +3459,8 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 			/* Get the column's name in case we need to output an error */
 			if (key->partattrs[i] != 0)
-				colname = get_relid_attribute_name(RelationGetRelid(parent),
-												   key->partattrs[i]);
+				colname = get_attname(RelationGetRelid(parent),
+									  key->partattrs[i], false);
 			else
 			{
 				colname = deparse_expression((Node *) list_nth(partexprs, j),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9cdbb06add..617d024144 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -908,8 +908,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 
 				if (i > 0)
 					appendStringInfoString(&buf, ", ");
-				attname = get_relid_attribute_name(trigrec->tgrelid,
-												   trigrec->tgattr.values[i]);
+				attname = get_attname(trigrec->tgrelid,
+									  trigrec->tgattr.values[i],
+									  false);
 				appendStringInfoString(&buf, quote_identifier(attname));
 			}
 		}
@@ -1290,7 +1291,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
 				appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(indrelid, attnum,
@@ -1533,7 +1534,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 		if (colno > 0)
 			appendStringInfoString(&buf, ", ");
 
-		attname = get_relid_attribute_name(statextrec->stxrelid, attnum);
+		attname = get_attname(statextrec->stxrelid, attnum, false);
 
 		appendStringInfoString(&buf, quote_identifier(attname));
 	}
@@ -1690,7 +1691,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(relid, attnum);
+			attname = get_attname(relid, attnum, false);
 			appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
@@ -2193,7 +2194,7 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 	{
 		char	   *colName;
 
-		colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
 			appendStringInfoString(buf, quote_identifier(colName));
@@ -6004,8 +6005,9 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -6308,8 +6310,9 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -10329,8 +10332,9 @@ processIndirection(Node *node, deparse_context *context)
 			 * target lists, but this function cannot be used for that case.
 			 */
 			Assert(list_length(fstore->fieldnums) == 1);
-			fieldname = get_relid_attribute_name(typrelid,
-												 linitial_int(fstore->fieldnums));
+			fieldname = get_attname(typrelid,
+									linitial_int(fstore->fieldnums),
+									false);
 			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
 
 			/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index e8aa179347..21e390336e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -768,10 +768,11 @@ get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum)
  *		Given the relation id and the attribute number,
  *		return the "attname" field from the attribute relation.
  *
- * Note: returns a palloc'd copy of the string, or NULL if no such attribute.
+ * Note: returns a palloc'd copy of the string. In the event if an error,
+ * return NULL if missing_ok is true.
  */
 char *
-get_attname(Oid relid, AttrNumber attnum)
+get_attname(Oid relid, AttrNumber attnum, bool missing_ok)
 {
 	HeapTuple	tp;
 
@@ -787,26 +788,12 @@ get_attname(Oid relid, AttrNumber attnum)
 		ReleaseSysCache(tp);
 		return result;
 	}
-	else
-		return NULL;
-}
 
-/*
- * get_relid_attribute_name
- *
- * Same as above routine get_attname(), except that error
- * is handled by elog() instead of returning NULL.
- */
-char *
-get_relid_attribute_name(Oid relid, AttrNumber attnum)
-{
-	char	   *attname;
+	if (missing_ok)
+		return NULL;
 
-	attname = get_attname(relid, attnum);
-	if (attname == NULL)
-		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
-			 attnum, relid);
-	return attname;
+	elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+		 attnum, relid);
 }
 
 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00ba33bfb4..2a1f612a78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5236,7 +5236,7 @@ errtablecol(Relation rel, int attnum)
 	if (attnum > 0 && attnum <= reldesc->natts)
 		colname = NameStr(TupleDescAttr(reldesc, attnum - 1)->attname);
 	else
-		colname = get_relid_attribute_name(RelationGetRelid(rel), attnum);
+		colname = get_attname(RelationGetRelid(rel), attnum, false);
 
 	return errtablecolname(rel, colname);
 }
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 9731e6f7ae..1f6c04a8f3 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -83,8 +83,7 @@ extern List *get_op_btree_interpretation(Oid opno);
 extern bool equality_ops_are_compatible(Oid opno1, Oid opno2);
 extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
 				  int16 procnum);
-extern char *get_attname(Oid relid, AttrNumber attnum);
-extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
+extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok);
 extern AttrNumber get_attnum(Oid relid, const char *attname);
 extern char get_attidentity(Oid relid, AttrNumber attnum);
 extern Oid	get_atttype(Oid relid, AttrNumber attnum);
-- 
2.15.1

0002-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/plain; charset=us-asciiDownload
From a929b9436237972fe25963879d591d19aaf70353 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 12 Jan 2018 10:59:41 +0900
Subject: [PATCH 2/4] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object.
---
 contrib/dblink/dblink.c             |  2 +-
 contrib/file_fdw/file_fdw.c         |  4 ++--
 contrib/postgres_fdw/connection.c   |  4 ++--
 contrib/postgres_fdw/postgres_fdw.c |  8 ++++----
 doc/src/sgml/fdwhandler.sgml        | 10 ++++++++--
 src/backend/catalog/objectaddress.c | 12 ++++++------
 src/backend/commands/foreigncmds.c  | 14 ++++++++------
 src/backend/commands/tablecmds.c    |  8 ++++----
 src/backend/foreign/foreign.c       | 24 ++++++++++++++++++++----
 src/include/foreign/foreign.h       |  4 ++--
 10 files changed, 57 insertions(+), 33 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index a6c897c319..7e7c126665 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2784,7 +2784,7 @@ get_connect_string(const char *servername)
 		Oid			userid = GetUserId();
 
 		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
+		fdw = GetForeignDataWrapper(fdwid, false);
 
 		/* Check permissions, user must have usage on the server. */
 		aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index cf0a3629bc..d837f977e8 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -357,8 +357,8 @@ fileGetOptions(Oid foreigntableid,
 	 * Simplify?)
 	 */
 	table = GetForeignTable(foreigntableid);
-	server = GetForeignServer(table->serverid);
-	wrapper = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(table->serverid, false);
+	wrapper = GetForeignDataWrapper(server->fdwid, false);
 
 	options = NIL;
 	options = list_concat(options, wrapper->options);
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 00c926b983..3503595b7b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -182,7 +182,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
 	 */
 	if (entry->conn == NULL)
 	{
-		ForeignServer *server = GetForeignServer(user->serverid);
+		ForeignServer *server = GetForeignServer(user->serverid, false);
 
 		/* Reset all transient state fields, to be sure all are clean */
 		entry->xact_depth = 0;
@@ -1003,7 +1003,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
 	umform = (Form_pg_user_mapping) GETSTRUCT(tup);
-	server = GetForeignServer(umform->umserver);
+	server = GetForeignServer(umform->umserver, false);
 	ReleaseSysCache(tup);
 
 	ereport(ERROR,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 469e83a023..c92705d62d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -506,7 +506,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/* Look up foreign-table catalog info. */
 	fpinfo->table = GetForeignTable(foreigntableid);
-	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+	fpinfo->server = GetForeignServer(fpinfo->table->serverid, false);
 
 	/*
 	 * Extract user-settable option values.  Note that per-table setting of
@@ -2039,7 +2039,7 @@ postgresIsForeignRelUpdatable(Relation rel)
 	updatable = true;
 
 	table = GetForeignTable(RelationGetRelid(rel));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 
 	foreach(lc, server->options)
 	{
@@ -3589,7 +3589,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	 * owner, even if the ANALYZE was started by some other user.
 	 */
 	table = GetForeignTable(RelationGetRelid(relation));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false);
 
@@ -3812,7 +3812,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	server = GetForeignServer(serverOid);
+	server = GetForeignServer(serverOid, false);
 	mapping = GetUserMapping(GetUserId(), server->serverid);
 	conn = GetConnection(mapping, false);
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 0ed3a47233..af6b5822f6 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1333,25 +1333,31 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid);
+GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignDataWrapper</structname>
      object for the foreign-data wrapper with the given OID.  A
      <structname>ForeignDataWrapper</structname> object contains properties
      of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign-data wrapper.
     </para>
 
     <para>
 <programlisting>
 ForeignServer *
-GetForeignServer(Oid serverid);
+GetForeignServer(Oid serverid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignServer</structname> object
      for the foreign server with the given OID.  A
      <structname>ForeignServer</structname> object contains properties
      of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
     </para>
 
     <para>
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 29147feb6b..476aff22e6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3193,7 +3193,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
@@ -3202,7 +3202,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
@@ -3222,7 +3222,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
@@ -4699,7 +4699,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
 				if (objname)
 					*objname = list_make1(pstrdup(fdw->fdwname));
@@ -4710,7 +4710,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(srv->servername));
 				if (objname)
@@ -4733,7 +4733,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 44f3da9b51..7e4a6daa92 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -368,7 +368,8 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
 			if (aclresult != ACLCHECK_OK)
 			{
-				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw,
+																false);
 
 				aclcheck_error(aclresult, ACL_KIND_FDW, fdw->fdwname);
 			}
@@ -1033,7 +1034,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	if (stmt->options)
 	{
-		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw,
+														false);
 		Datum		datum;
 		bool		isnull;
 
@@ -1187,7 +1189,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 							stmt->servername)));
 	}
 
-	fdw = GetForeignDataWrapper(srv->fdwid);
+	fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_user_mapping.
@@ -1299,7 +1301,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 		 * Process the options.
 		 */
 
-		fdw = GetForeignDataWrapper(srv->fdwid);
+		fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
 								tp,
@@ -1479,7 +1481,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, ACL_KIND_FOREIGN_SERVER, server->servername);
 
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_foreign_table.
@@ -1542,7 +1544,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 	(void) LookupCreationNamespace(stmt->local_schema);
 
 	/* Get the FDW and check it supports IMPORT */
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 	if (!OidIsValid(fdw->fdwhandler))
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f2a928b823..9eb81b0718 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9460,8 +9460,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(fttableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(fttableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	heap_close(ftrel, AccessShareLock);
 	ReleaseSysCache(tuple);
@@ -12376,8 +12376,8 @@ ATExecGenericOptions(Relation rel, List *options)
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(tableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(tableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	memset(repl_val, 0, sizeof(repl_val));
 	memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index e7fd507fa5..3928c014bc 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -32,7 +32,7 @@
  * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
  */
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid)
+GetForeignDataWrapper(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +43,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -82,7 +86,11 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
 	if (!OidIsValid(fdwId))
 		return NULL;
 
-	return GetForeignDataWrapper(fdwId);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignDataWrapper(fdwId, false);
 }
 
 
@@ -90,7 +98,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  * GetForeignServer - look up the foreign server definition.
  */
 ForeignServer *
-GetForeignServer(Oid serverid)
+GetForeignServer(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +109,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
@@ -152,7 +164,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 	if (!OidIsValid(serverid))
 		return NULL;
 
-	return GetForeignServer(serverid);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignServer(serverid, false);
 }
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..df969d04ea 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -69,10 +69,10 @@ typedef struct ForeignTable
 } ForeignTable;
 
 
-extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServer(Oid serverid, bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
-extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.15.1

0003-Refactor-routines-for-subscription-and-publication-l.patchtext/plain; charset=us-asciiDownload
From e231c8b2570cd58852e414265822b320081d4bc8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 11:16:01 +0200
Subject: [PATCH 3/4] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  |  6 +++++-
 src/backend/catalog/pg_subscription.c |  6 +++++-
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 476aff22e6..5aa3d8ae94 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3374,7 +3374,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3391,7 +3392,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3402,7 +3403,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4899,7 +4900,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4920,7 +4921,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4939,7 +4940,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index b4a5f48b4e..7b9808c952 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -419,7 +419,7 @@ get_publication_oid(const char *pubname, bool missing_ok)
  * get_publication_name - given a publication Oid, look up the name
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -428,7 +428,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8e16d3b7bc..18807d7d43 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -181,7 +181,7 @@ get_subscription_oid(const char *subname, bool missing_ok)
  * get_subscription_name - given a subscription OID, look up the name
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +190,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 7bdc634cf3..bd960de835 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -97,7 +97,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 46d0b48232..307893b1f1 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -89,7 +89,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.15.1

0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/plain; charset=us-asciiDownload
From d4f29d22e690640e759df5a3fdde3a3d9cd9d174 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 12 Jan 2018 10:55:12 +0900
Subject: [PATCH 4/4] Eliminate user-visible cache lookup errors for objaddr 
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 824 ++++++++++++++++++++-------
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/format_type.c          |  23 +-
 src/backend/utils/adt/regproc.c              |  16 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/builtins.h                 |   3 +
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 ++++
 21 files changed, 1541 insertions(+), 290 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 10f65a071e..9096992730 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -186,7 +186,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -263,7 +263,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 7554017923..ee591ca10b 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -372,7 +372,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -543,7 +543,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2428434030..cce010fdd6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17546,7 +17546,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17562,7 +17563,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17577,6 +17579,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 269111b4c1..87531a594a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -607,11 +607,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -682,11 +682,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -777,12 +777,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -876,7 +876,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -899,7 +899,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -957,7 +958,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 5aa3d8ae94..870bc0f8aa 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -754,14 +754,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1627,7 +1633,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1658,7 +1664,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2656,10 +2662,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2668,22 +2676,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  false, missing_ok, false));
 			break;
 
 		case OCLASS_CAST:
@@ -2706,15 +2720,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2728,13 +2746,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2746,8 +2769,14 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2755,7 +2784,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2777,8 +2806,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2806,18 +2840,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2825,9 +2863,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2835,6 +2879,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2850,8 +2895,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2878,7 +2928,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2888,8 +2938,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2918,28 +2973,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2968,28 +3027,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3016,15 +3079,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3051,15 +3118,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3072,8 +3143,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3086,8 +3161,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
+				{
+					if (missing_ok)
+						break;
+					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3105,8 +3184,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3120,8 +3203,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3135,8 +3222,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3150,8 +3241,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3160,8 +3255,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3171,8 +3269,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3183,8 +3285,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3193,8 +3299,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3202,8 +3309,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3218,8 +3326,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -3257,8 +3370,16 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3315,8 +3436,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3328,8 +3453,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3356,15 +3485,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3373,9 +3507,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3388,8 +3524,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3402,8 +3542,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3415,8 +3557,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3434,6 +3580,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3449,14 +3599,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3466,7 +3616,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3526,7 +3680,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3536,7 +3690,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3580,7 +3738,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3595,6 +3757,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3668,8 +3832,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3692,8 +3862,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3710,6 +3886,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3737,23 +3914,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
+	else
+	{
+		pfree(identity);
 
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3767,7 +3958,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3777,11 +3968,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3797,7 +3990,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3934,6 +4128,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3941,7 +4139,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3949,7 +4148,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			Assert(buffer->len == 0);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -3995,7 +4201,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4004,7 +4210,14 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
+	{
+		if (missing_ok)
+		{
+			heap_close(constrRel, AccessShareLock);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4022,7 +4235,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4030,7 +4244,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->proisagg)
@@ -4047,12 +4265,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4061,11 +4280,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4087,32 +4308,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId,
-								   object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1, false,
+											   missing_ok, true);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4130,8 +4360,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(castRel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4158,8 +4395,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4180,15 +4421,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4203,7 +4449,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4222,8 +4469,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4258,19 +4509,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4285,8 +4541,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for language %u",
 						 object->objectId);
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4303,10 +4563,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4320,8 +4581,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4347,7 +4612,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4356,8 +4622,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4389,13 +4659,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amopDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4443,13 +4722,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amprocDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4483,14 +4771,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(ruleDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4509,14 +4804,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(trigDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4530,8 +4832,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4548,8 +4854,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4571,8 +4881,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4594,8 +4908,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4617,8 +4935,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4640,8 +4962,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4658,7 +4984,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4672,8 +5000,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4687,8 +5019,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4700,10 +5036,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4711,11 +5050,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4730,8 +5072,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -4778,8 +5124,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4841,8 +5195,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4861,8 +5219,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4881,14 +5243,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(polDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4900,11 +5269,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4917,8 +5289,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4928,7 +5304,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4940,11 +5317,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4961,8 +5341,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(transformDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -4988,20 +5375,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5011,7 +5398,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5040,7 +5431,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5049,7 +5441,15 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			if (object)
+				*object = NIL;
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 9dfbe123b5..a8fa709d08 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 8455138ed3..9e7bbc7488 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1434,10 +1434,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2095,8 +2096,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index c0c933583f..0d29a7c443 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9eb81b0718..ff4fa5165c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9162,7 +9162,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9210,7 +9210,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9229,7 +9229,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9247,7 +9247,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9308,7 +9308,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index de3da3607a..d86576f60a 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -28,9 +28,6 @@
 
 #define MAX_INT32_LEN 11
 
-static char *format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify);
 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 
@@ -74,11 +71,11 @@ format_type(PG_FUNCTION_ARGS)
 	type_oid = PG_GETARG_OID(0);
 
 	if (PG_ARGISNULL(1))
-		result = format_type_internal(type_oid, -1, false, true, false);
+		result = format_type_extended(type_oid, -1, false, true, false);
 	else
 	{
 		typemod = PG_GETARG_INT32(1);
-		result = format_type_internal(type_oid, typemod, true, true, false);
+		result = format_type_extended(type_oid, typemod, true, true, false);
 	}
 
 	PG_RETURN_TEXT_P(cstring_to_text(result));
@@ -93,7 +90,7 @@ format_type(PG_FUNCTION_ARGS)
 char *
 format_type_be(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, false);
+	return format_type_extended(type_oid, -1, false, false, false);
 }
 
 /*
@@ -103,7 +100,7 @@ format_type_be(Oid type_oid)
 char *
 format_type_be_qualified(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, true);
+	return format_type_extended(type_oid, -1, false, false, true);
 }
 
 /*
@@ -112,7 +109,7 @@ format_type_be_qualified(Oid type_oid)
 char *
 format_type_with_typemod(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, false);
+	return format_type_extended(type_oid, typemod, true, false, false);
 }
 
 /*
@@ -122,14 +119,14 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 char *
 format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, true);
+	return format_type_extended(type_oid, typemod, true, false, true);
 }
 
 /*
  * Common workhorse.
  */
-static char *
-format_type_internal(Oid type_oid, int32 typemod,
+char *
+format_type_extended(Oid type_oid, int32 typemod,
 					 bool typemod_given, bool allow_invalid,
 					 bool force_qualify)
 {
@@ -141,7 +138,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 	char	   *buf;
 
 	if (type_oid == InvalidOid && allow_invalid)
-		return pstrdup("-");
+			return pstrdup("-");
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
@@ -437,7 +434,7 @@ oidvectortypes(PG_FUNCTION_ARGS)
 
 	for (num = 0; num < numargs; num++)
 	{
-		char	   *typename = format_type_internal(oidArray->values[num], -1,
+		char	   *typename = format_type_extended(oidArray->values[num], -1,
 													false, true, false);
 		size_t		slen = strlen(typename);
 
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..834a95084c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for operator with OID %u",
 			 operator_oid);
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 834554ec17..4525423f90 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 #endif							/* OBJECTADDRESS_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8bb57c5829..090d6b3a1e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -116,6 +116,9 @@ extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
+extern char *format_type_extended(Oid type_oid, int32 typemod,
+								  bool typemod_given, bool allow_invalid,
+								  bool force_qualify);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
 /* quote.c */
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index bfd9d54c11..d74715eff7 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,3 +492,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 13 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 55faa71edf..0772173775 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -213,3 +213,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.15.1

#16Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#15)
4 attachment(s)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Fri, Jan 12, 2018 at 11:01:59AM +0900, Michael Paquier wrote:

Thanks Mr. Robot and Thomas for the reminder. Attached is an updated
patch set taking care of those warnings, 0002 and 0004 being impacted.

The last patch set has rotten enough for git am to complain (not patch
-p1), so attached is a new set. Alvaro, I am aware that there are way
more shiny features than this patch set, still are you planning to look
at this patch set aimed at doing the cleanup of the existing inferface
you introduced?

I am moving it to the next CF for now..
--
Michael

Attachments:

0001-Refactor-syscache-routines-to-get-attribute-name.patchtext/x-diff; charset=us-asciiDownload
From 39775de4545ac73276f517d6fcff006adf406445 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 10:12:25 +0200
Subject: [PATCH 1/4] Refactor syscache routines to get attribute name

The existing routines are merged, and a missing_ok argument to return a
NULL result instead of an error is added to give more control to the
user. This unified interface will get used for object addresses.
---
 contrib/postgres_fdw/deparse.c      |  2 +-
 contrib/postgres_fdw/postgres_fdw.c |  2 +-
 contrib/sepgsql/dml.c               |  2 +-
 src/backend/catalog/heap.c          |  3 ++-
 src/backend/catalog/objectaddress.c | 10 ++++++----
 src/backend/parser/parse_relation.c |  2 +-
 src/backend/parser/parse_utilcmd.c  | 11 ++++++-----
 src/backend/utils/adt/ruleutils.c   | 28 ++++++++++++++++------------
 src/backend/utils/cache/lsyscache.c | 27 +++++++--------------------
 src/backend/utils/cache/relcache.c  |  2 +-
 src/include/utils/lsyscache.h       |  3 +--
 11 files changed, 43 insertions(+), 49 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index e111b09c7c..eb6117e665 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2026,7 +2026,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root,
 		 * FDW option, use attribute name.
 		 */
 		if (colname == NULL)
-			colname = get_relid_attribute_name(rte->relid, varattno);
+			colname = get_attname(rte->relid, varattno, false);
 
 		if (qualify_col)
 			ADD_REL_QUALIFIER(buf, varno);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 7ff43337a9..46e066202b 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5139,7 +5139,7 @@ conversion_error_callback(void *arg)
 			if (var->varattno == 0)
 				is_wholerow = true;
 			else
-				attname = get_relid_attribute_name(rte->relid, var->varattno);
+				attname = get_attname(rte->relid, var->varattno, false);
 
 			relname = get_rel_name(rte->relid);
 		}
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 36cdb27a76..10f65a071e 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -118,7 +118,7 @@ fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
 			continue;
 		}
 
-		attname = get_attname(parentId, attno);
+		attname = get_attname(parentId, attno, true);
 		if (!attname)
 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
 				 attno, parentId);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 774c07b03a..637c5a03ad 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2405,7 +2405,8 @@ AddRelationNewConstraints(Relation rel,
 
 			if (list_length(vars) == 1)
 				colname = get_attname(RelationGetRelid(rel),
-									  ((Var *) linitial(vars))->varattno);
+									  ((Var *) linitial(vars))->varattno,
+									  true);
 			else
 				colname = NULL;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 570e65affb..815bb77f00 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2682,8 +2682,9 @@ getObjectDescription(const ObjectAddress *object)
 			getRelationDescription(&buffer, object->objectId);
 			if (object->objectSubId != 0)
 				appendStringInfo(&buffer, _(" column %s"),
-								 get_relid_attribute_name(object->objectId,
-														  object->objectSubId));
+								 get_attname(object->objectId,
+											 object->objectSubId,
+											 false));
 			break;
 
 		case OCLASS_PROC:
@@ -4103,8 +4104,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *attr;
 
-				attr = get_relid_attribute_name(object->objectId,
-												object->objectSubId);
+				attr = get_attname(object->objectId,
+								   object->objectSubId,
+								   false);
 				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
 				if (objname)
 					*objname = lappend(*objname, attr);
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 2625da5327..053ae02c9f 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2687,7 +2687,7 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+		return get_attname(rte->relid, attnum, false);
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1d35815fcf..889db59e8b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1462,7 +1462,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
 			/* Simple index column */
 			char	   *attname;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			keycoltype = get_atttype(indrelid, attnum);
 
 			iparam->name = attname;
@@ -3398,8 +3398,9 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 		/* Get the only column's name in case we need to output an error */
 		if (key->partattrs[0] != 0)
-			colname = get_relid_attribute_name(RelationGetRelid(parent),
-											   key->partattrs[0]);
+			colname = get_attname(RelationGetRelid(parent),
+								  key->partattrs[0],
+								  false);
 		else
 			colname = deparse_expression((Node *) linitial(partexprs),
 										 deparse_context_for(RelationGetRelationName(parent),
@@ -3483,8 +3484,8 @@ transformPartitionBound(ParseState *pstate, Relation parent,
 
 			/* Get the column's name in case we need to output an error */
 			if (key->partattrs[i] != 0)
-				colname = get_relid_attribute_name(RelationGetRelid(parent),
-												   key->partattrs[i]);
+				colname = get_attname(RelationGetRelid(parent),
+									  key->partattrs[i], false);
 			else
 			{
 				colname = deparse_expression((Node *) list_nth(partexprs, j),
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c5f5a1ca3f..259af257b5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -908,8 +908,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 
 				if (i > 0)
 					appendStringInfoString(&buf, ", ");
-				attname = get_relid_attribute_name(trigrec->tgrelid,
-												   trigrec->tgattr.values[i]);
+				attname = get_attname(trigrec->tgrelid,
+									  trigrec->tgattr.values[i],
+									  false);
 				appendStringInfoString(&buf, quote_identifier(attname));
 			}
 		}
@@ -1292,7 +1293,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(indrelid, attnum);
+			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
 				appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(indrelid, attnum,
@@ -1535,7 +1536,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool missing_ok)
 		if (colno > 0)
 			appendStringInfoString(&buf, ", ");
 
-		attname = get_relid_attribute_name(statextrec->stxrelid, attnum);
+		attname = get_attname(statextrec->stxrelid, attnum, false);
 
 		appendStringInfoString(&buf, quote_identifier(attname));
 	}
@@ -1692,7 +1693,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			char	   *attname;
 			int32		keycoltypmod;
 
-			attname = get_relid_attribute_name(relid, attnum);
+			attname = get_attname(relid, attnum, false);
 			appendStringInfoString(&buf, quote_identifier(attname));
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
@@ -2196,7 +2197,7 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 	{
 		char	   *colName;
 
-		colName = get_relid_attribute_name(relId, DatumGetInt16(keys[j]));
+		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
 			appendStringInfoString(buf, quote_identifier(colName));
@@ -6007,8 +6008,9 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -6311,8 +6313,9 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
 		appendStringInfoString(buf,
-							   quote_identifier(get_relid_attribute_name(rte->relid,
-																		 tle->resno)));
+							   quote_identifier(get_attname(rte->relid,
+															tle->resno,
+															false)));
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -10332,8 +10335,9 @@ processIndirection(Node *node, deparse_context *context)
 			 * target lists, but this function cannot be used for that case.
 			 */
 			Assert(list_length(fstore->fieldnums) == 1);
-			fieldname = get_relid_attribute_name(typrelid,
-												 linitial_int(fstore->fieldnums));
+			fieldname = get_attname(typrelid,
+									linitial_int(fstore->fieldnums),
+									false);
 			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
 
 			/*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index e8aa179347..21e390336e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -768,10 +768,11 @@ get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype, int16 procnum)
  *		Given the relation id and the attribute number,
  *		return the "attname" field from the attribute relation.
  *
- * Note: returns a palloc'd copy of the string, or NULL if no such attribute.
+ * Note: returns a palloc'd copy of the string. In the event if an error,
+ * return NULL if missing_ok is true.
  */
 char *
-get_attname(Oid relid, AttrNumber attnum)
+get_attname(Oid relid, AttrNumber attnum, bool missing_ok)
 {
 	HeapTuple	tp;
 
@@ -787,26 +788,12 @@ get_attname(Oid relid, AttrNumber attnum)
 		ReleaseSysCache(tp);
 		return result;
 	}
-	else
-		return NULL;
-}
 
-/*
- * get_relid_attribute_name
- *
- * Same as above routine get_attname(), except that error
- * is handled by elog() instead of returning NULL.
- */
-char *
-get_relid_attribute_name(Oid relid, AttrNumber attnum)
-{
-	char	   *attname;
+	if (missing_ok)
+		return NULL;
 
-	attname = get_attname(relid, attnum);
-	if (attname == NULL)
-		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
-			 attnum, relid);
-	return attname;
+	elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+		 attnum, relid);
 }
 
 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c081b88b73..cbece1f478 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5245,7 +5245,7 @@ errtablecol(Relation rel, int attnum)
 	if (attnum > 0 && attnum <= reldesc->natts)
 		colname = NameStr(TupleDescAttr(reldesc, attnum - 1)->attname);
 	else
-		colname = get_relid_attribute_name(RelationGetRelid(rel), attnum);
+		colname = get_attname(RelationGetRelid(rel), attnum, false);
 
 	return errtablecolname(rel, colname);
 }
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 9731e6f7ae..1f6c04a8f3 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -83,8 +83,7 @@ extern List *get_op_btree_interpretation(Oid opno);
 extern bool equality_ops_are_compatible(Oid opno1, Oid opno2);
 extern Oid get_opfamily_proc(Oid opfamily, Oid lefttype, Oid righttype,
 				  int16 procnum);
-extern char *get_attname(Oid relid, AttrNumber attnum);
-extern char *get_relid_attribute_name(Oid relid, AttrNumber attnum);
+extern char *get_attname(Oid relid, AttrNumber attnum, bool missing_ok);
 extern AttrNumber get_attnum(Oid relid, const char *attname);
 extern char get_attidentity(Oid relid, AttrNumber attnum);
 extern Oid	get_atttype(Oid relid, AttrNumber attnum);
-- 
2.15.1

0002-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From d7c4c85bfbdd34e907a792b98170090792d28b40 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 1 Feb 2018 15:24:13 +0900
Subject: [PATCH 2/4] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object.
---
 contrib/dblink/dblink.c             |  2 +-
 contrib/file_fdw/file_fdw.c         |  4 ++--
 contrib/postgres_fdw/connection.c   |  4 ++--
 contrib/postgres_fdw/postgres_fdw.c |  8 ++++----
 doc/src/sgml/fdwhandler.sgml        | 10 ++++++++--
 src/backend/catalog/objectaddress.c | 12 ++++++------
 src/backend/commands/foreigncmds.c  | 14 ++++++++------
 src/backend/commands/tablecmds.c    |  8 ++++----
 src/backend/foreign/foreign.c       | 24 ++++++++++++++++++++----
 src/include/foreign/foreign.h       |  4 ++--
 10 files changed, 57 insertions(+), 33 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index ae7e24ad08..a67febac6f 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2784,7 +2784,7 @@ get_connect_string(const char *servername)
 		Oid			userid = GetUserId();
 
 		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
+		fdw = GetForeignDataWrapper(fdwid, false);
 
 		/* Check permissions, user must have usage on the server. */
 		aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index cf0a3629bc..d837f977e8 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -357,8 +357,8 @@ fileGetOptions(Oid foreigntableid,
 	 * Simplify?)
 	 */
 	table = GetForeignTable(foreigntableid);
-	server = GetForeignServer(table->serverid);
-	wrapper = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(table->serverid, false);
+	wrapper = GetForeignDataWrapper(server->fdwid, false);
 
 	options = NIL;
 	options = list_concat(options, wrapper->options);
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 00c926b983..3503595b7b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -182,7 +182,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
 	 */
 	if (entry->conn == NULL)
 	{
-		ForeignServer *server = GetForeignServer(user->serverid);
+		ForeignServer *server = GetForeignServer(user->serverid, false);
 
 		/* Reset all transient state fields, to be sure all are clean */
 		entry->xact_depth = 0;
@@ -1003,7 +1003,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
 	umform = (Form_pg_user_mapping) GETSTRUCT(tup);
-	server = GetForeignServer(umform->umserver);
+	server = GetForeignServer(umform->umserver, false);
 	ReleaseSysCache(tup);
 
 	ereport(ERROR,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 46e066202b..b3949dc3be 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -506,7 +506,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/* Look up foreign-table catalog info. */
 	fpinfo->table = GetForeignTable(foreigntableid);
-	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+	fpinfo->server = GetForeignServer(fpinfo->table->serverid, false);
 
 	/*
 	 * Extract user-settable option values.  Note that per-table setting of
@@ -2039,7 +2039,7 @@ postgresIsForeignRelUpdatable(Relation rel)
 	updatable = true;
 
 	table = GetForeignTable(RelationGetRelid(rel));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 
 	foreach(lc, server->options)
 	{
@@ -3589,7 +3589,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	 * owner, even if the ANALYZE was started by some other user.
 	 */
 	table = GetForeignTable(RelationGetRelid(relation));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false);
 
@@ -3812,7 +3812,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	server = GetForeignServer(serverOid);
+	server = GetForeignServer(serverOid, false);
 	mapping = GetUserMapping(GetUserId(), server->serverid);
 	conn = GetConnection(mapping, false);
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 0ed3a47233..af6b5822f6 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1333,25 +1333,31 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid);
+GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignDataWrapper</structname>
      object for the foreign-data wrapper with the given OID.  A
      <structname>ForeignDataWrapper</structname> object contains properties
      of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign-data wrapper.
     </para>
 
     <para>
 <programlisting>
 ForeignServer *
-GetForeignServer(Oid serverid);
+GetForeignServer(Oid serverid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignServer</structname> object
      for the foreign server with the given OID.  A
      <structname>ForeignServer</structname> object contains properties
      of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
     </para>
 
     <para>
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 815bb77f00..a9361d39d0 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3204,7 +3204,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
@@ -3213,7 +3213,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
@@ -3233,7 +3233,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
@@ -4712,7 +4712,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
 				if (objname)
 					*objname = list_make1(pstrdup(fdw->fdwname));
@@ -4723,7 +4723,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(srv->servername));
 				if (objname)
@@ -4746,7 +4746,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 5c53aeeaeb..645e2efb74 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -368,7 +368,8 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
 			if (aclresult != ACLCHECK_OK)
 			{
-				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw,
+																false);
 
 				aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname);
 			}
@@ -1033,7 +1034,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	if (stmt->options)
 	{
-		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw,
+														false);
 		Datum		datum;
 		bool		isnull;
 
@@ -1187,7 +1189,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 							stmt->servername)));
 	}
 
-	fdw = GetForeignDataWrapper(srv->fdwid);
+	fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_user_mapping.
@@ -1299,7 +1301,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 		 * Process the options.
 		 */
 
-		fdw = GetForeignDataWrapper(srv->fdwid);
+		fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
 								tp,
@@ -1479,7 +1481,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, server->servername);
 
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_foreign_table.
@@ -1542,7 +1544,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 	(void) LookupCreationNamespace(stmt->local_schema);
 
 	/* Get the FDW and check it supports IMPORT */
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 	if (!OidIsValid(fdw->fdwhandler))
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 37c7d66881..9894b66296 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9534,8 +9534,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(fttableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(fttableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	heap_close(ftrel, AccessShareLock);
 	ReleaseSysCache(tuple);
@@ -12437,8 +12437,8 @@ ATExecGenericOptions(Relation rel, List *options)
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(tableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(tableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	memset(repl_val, 0, sizeof(repl_val));
 	memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index e7fd507fa5..3928c014bc 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -32,7 +32,7 @@
  * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
  */
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid)
+GetForeignDataWrapper(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +43,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -82,7 +86,11 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
 	if (!OidIsValid(fdwId))
 		return NULL;
 
-	return GetForeignDataWrapper(fdwId);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignDataWrapper(fdwId, false);
 }
 
 
@@ -90,7 +98,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  * GetForeignServer - look up the foreign server definition.
  */
 ForeignServer *
-GetForeignServer(Oid serverid)
+GetForeignServer(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +109,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
@@ -152,7 +164,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 	if (!OidIsValid(serverid))
 		return NULL;
 
-	return GetForeignServer(serverid);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignServer(serverid, false);
 }
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..df969d04ea 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -69,10 +69,10 @@ typedef struct ForeignTable
 } ForeignTable;
 
 
-extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServer(Oid serverid, bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
-extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.15.1

0003-Refactor-routines-for-subscription-and-publication-l.patchtext/x-diff; charset=us-asciiDownload
From d4ec526b30aba25027e8fb025d61a24f6e3f1acd Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 4 Aug 2017 11:16:01 +0200
Subject: [PATCH 3/4] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  |  6 +++++-
 src/backend/catalog/pg_subscription.c |  6 +++++-
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index a9361d39d0..7f5596c52b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3385,7 +3385,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3402,7 +3403,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3413,7 +3414,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4912,7 +4913,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4933,7 +4934,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4952,7 +4953,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index b4a5f48b4e..7b9808c952 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -419,7 +419,7 @@ get_publication_oid(const char *pubname, bool missing_ok)
  * get_publication_name - given a publication Oid, look up the name
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -428,7 +428,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8e16d3b7bc..18807d7d43 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -181,7 +181,7 @@ get_subscription_oid(const char *subname, bool missing_ok)
  * get_subscription_name - given a subscription OID, look up the name
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +190,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
+	{
+		if (missing_ok)
+			return NULL;
 		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 7bdc634cf3..bd960de835 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -97,7 +97,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 46d0b48232..307893b1f1 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -89,7 +89,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.15.1

0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From e051cec9c06e28a3f78f9c13f42db23863823e99 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 1 Feb 2018 15:25:21 +0900
Subject: [PATCH 4/4] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 824 ++++++++++++++++++++-------
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/format_type.c          |  23 +-
 src/backend/utils/adt/regproc.c              |  16 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/builtins.h                 |   3 +
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 ++++
 21 files changed, 1541 insertions(+), 290 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 10f65a071e..9096992730 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -186,7 +186,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -263,7 +263,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 7554017923..ee591ca10b 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -372,7 +372,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -543,7 +543,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 487c7ff750..b05ab11aae 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17546,7 +17546,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17562,7 +17563,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17577,6 +17579,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index be60270ea5..b18bb98649 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -608,11 +608,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -692,11 +692,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -788,12 +788,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -887,7 +887,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -910,7 +910,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -968,7 +969,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7f5596c52b..2cdf6f3b71 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -754,14 +754,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1628,7 +1634,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1659,7 +1665,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2667,10 +2673,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2679,22 +2687,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  false, missing_ok, false));
 			break;
 
 		case OCLASS_CAST:
@@ -2717,15 +2731,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2739,13 +2757,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2757,8 +2780,14 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2766,7 +2795,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2788,8 +2817,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
+
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2817,18 +2851,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2836,9 +2874,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2846,6 +2890,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2861,8 +2906,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2889,7 +2939,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2899,8 +2949,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2929,28 +2984,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2979,28 +3038,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3027,15 +3090,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3062,15 +3129,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3083,8 +3154,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3097,8 +3172,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
+				{
+					if (missing_ok)
+						break;
+					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3116,8 +3195,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3131,8 +3214,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3146,8 +3233,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3161,8 +3252,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3171,8 +3266,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3182,8 +3280,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3194,8 +3296,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3204,8 +3310,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3213,8 +3320,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3229,8 +3337,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -3268,8 +3381,16 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3326,8 +3447,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3339,8 +3464,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3367,15 +3496,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3384,9 +3518,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3399,8 +3535,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3413,8 +3553,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3426,8 +3568,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3445,6 +3591,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3460,14 +3610,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3477,7 +3627,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3538,7 +3692,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3548,7 +3702,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3592,7 +3750,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3607,6 +3769,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3680,8 +3844,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3704,8 +3874,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3722,6 +3898,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3749,23 +3926,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
+	else
+	{
+		pfree(identity);
 
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3779,7 +3970,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3789,11 +3980,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3809,7 +4002,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3946,6 +4140,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3953,7 +4151,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3961,7 +4160,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			Assert(buffer->len == 0);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4008,7 +4214,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4017,7 +4223,14 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
+	{
+		if (missing_ok)
+		{
+			heap_close(constrRel, AccessShareLock);
+			return;
+		}
 		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4035,7 +4248,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4043,7 +4257,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->proisagg)
@@ -4060,12 +4278,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4074,11 +4293,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4100,32 +4321,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId,
-								   object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1, false,
+											   missing_ok, true);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4143,8 +4373,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(castRel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4171,8 +4408,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4193,15 +4434,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4216,7 +4462,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4235,8 +4482,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for conversion %u",
 						 object->objectId);
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4271,19 +4522,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4298,8 +4554,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for language %u",
 						 object->objectId);
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4316,10 +4576,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4333,8 +4594,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for opclass %u",
 						 object->objectId);
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4360,7 +4625,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4369,8 +4635,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for access method %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4402,13 +4672,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amopDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4456,13 +4735,22 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(amscan);
+						heap_close(amprocDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4496,14 +4784,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(ruleDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4522,14 +4817,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(trigDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4543,8 +4845,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for namespace %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4561,8 +4867,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for statistics object %u",
 						 object->objectId);
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4584,8 +4894,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search parser %u",
 						 object->objectId);
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4607,8 +4921,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search dictionary %u",
 						 object->objectId);
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4630,8 +4948,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search template %u",
 						 object->objectId);
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4653,8 +4975,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for text search configuration %u",
 						 object->objectId);
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4671,7 +4997,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4685,8 +5013,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for database %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4700,8 +5032,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for tablespace %u",
 						 object->objectId);
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4713,10 +5049,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4724,11 +5063,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4743,8 +5085,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for user mapping %u",
 						 object->objectId);
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -4791,8 +5137,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						systable_endscan(rcscan);
+						heap_close(defaclrel, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for default ACL %u",
 						 object->objectId);
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4854,8 +5208,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for extension %u",
 						 object->objectId);
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4874,8 +5232,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for event trigger %u",
 						 object->objectId);
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4894,14 +5256,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(polDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4913,11 +5282,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4930,8 +5302,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+						break;
 					elog(ERROR, "cache lookup failed for publication table %u",
 						 object->objectId);
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4941,7 +5317,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4953,11 +5330,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4974,8 +5354,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
+				{
+					if (missing_ok)
+					{
+						heap_close(transformDesc, AccessShareLock);
+						break;
+					}
 					elog(ERROR, "could not find tuple for transform %u",
 						 object->objectId);
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5001,20 +5388,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5024,7 +5411,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5053,7 +5444,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5062,7 +5454,15 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
+	{
+		if (missing_ok)
+		{
+			if (object)
+				*object = NIL;
+			return;
+		}
 		elog(ERROR, "cache lookup failed for relation %u", relid);
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 549c7ea51d..e5faf5e0f5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1399,10 +1399,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2060,8 +2061,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2e4538146d..a4c622ee11 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9894b66296..901556b907 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9236,7 +9236,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9284,7 +9284,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9303,7 +9303,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9321,7 +9321,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9382,7 +9382,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index de3da3607a..d86576f60a 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -28,9 +28,6 @@
 
 #define MAX_INT32_LEN 11
 
-static char *format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify);
 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 
@@ -74,11 +71,11 @@ format_type(PG_FUNCTION_ARGS)
 	type_oid = PG_GETARG_OID(0);
 
 	if (PG_ARGISNULL(1))
-		result = format_type_internal(type_oid, -1, false, true, false);
+		result = format_type_extended(type_oid, -1, false, true, false);
 	else
 	{
 		typemod = PG_GETARG_INT32(1);
-		result = format_type_internal(type_oid, typemod, true, true, false);
+		result = format_type_extended(type_oid, typemod, true, true, false);
 	}
 
 	PG_RETURN_TEXT_P(cstring_to_text(result));
@@ -93,7 +90,7 @@ format_type(PG_FUNCTION_ARGS)
 char *
 format_type_be(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, false);
+	return format_type_extended(type_oid, -1, false, false, false);
 }
 
 /*
@@ -103,7 +100,7 @@ format_type_be(Oid type_oid)
 char *
 format_type_be_qualified(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, true);
+	return format_type_extended(type_oid, -1, false, false, true);
 }
 
 /*
@@ -112,7 +109,7 @@ format_type_be_qualified(Oid type_oid)
 char *
 format_type_with_typemod(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, false);
+	return format_type_extended(type_oid, typemod, true, false, false);
 }
 
 /*
@@ -122,14 +119,14 @@ format_type_with_typemod(Oid type_oid, int32 typemod)
 char *
 format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, true);
+	return format_type_extended(type_oid, typemod, true, false, true);
 }
 
 /*
  * Common workhorse.
  */
-static char *
-format_type_internal(Oid type_oid, int32 typemod,
+char *
+format_type_extended(Oid type_oid, int32 typemod,
 					 bool typemod_given, bool allow_invalid,
 					 bool force_qualify)
 {
@@ -141,7 +138,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 	char	   *buf;
 
 	if (type_oid == InvalidOid && allow_invalid)
-		return pstrdup("-");
+			return pstrdup("-");
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
@@ -437,7 +434,7 @@ oidvectortypes(PG_FUNCTION_ARGS)
 
 	for (num = 0; num < numargs; num++)
 	{
-		char	   *typename = format_type_internal(oidArray->values[num], -1,
+		char	   *typename = format_type_extended(oidArray->values[num], -1,
 													false, true, false);
 		size_t		slen = strlen(typename);
 
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..834a95084c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
+	{
+		if (missing_ok)
+			return;
 		elog(ERROR, "cache lookup failed for operator with OID %u",
 			 operator_oid);
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8bb57c5829..090d6b3a1e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -116,6 +116,9 @@ extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
 extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
+extern char *format_type_extended(Oid type_oid, int32 typemod,
+								  bool typemod_given, bool allow_invalid,
+								  bool force_qualify);
 extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
 /* quote.c */
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index bfd9d54c11..d74715eff7 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,3 +492,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 13 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 55faa71edf..0772173775 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -213,3 +213,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.15.1

#17Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Michael Paquier (#16)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

Michael Paquier wrote:

On Fri, Jan 12, 2018 at 11:01:59AM +0900, Michael Paquier wrote:

Thanks Mr. Robot and Thomas for the reminder. Attached is an updated
patch set taking care of those warnings, 0002 and 0004 being impacted.

The last patch set has rotten enough for git am to complain (not patch
-p1), so attached is a new set.

Pushed 0001, which was easy enough to deal with. I think 0002 and 0003
should be changed similarly: the elog(ERROR) code should be inside "if"
and the "return NULL" case the straight path, rather than the other way
around. That seems more robust than the compiler relying on knowledge
that elog(ERROR) does not return.

As far as format_type_extended() is concerned, IMO we've gone far enough
with the number of variants of format_type(). Making the function
public makes sense to me, but let's add a bits32 flags argument instead
of exposing the messy set of booleans. We can add compatibility
wrappers for the flag combinations most used in core code, and maybe
take the opportunity phase out the uncommon ones.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#18Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#17)
4 attachment(s)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Mon, Feb 12, 2018 at 07:57:34PM -0300, Alvaro Herrera wrote:

Pushed 0001, which was easy enough to deal with.

Thanks.

I think 0002 and 0003 should be changed similarly: the elog(ERROR)
code should be inside "if" and the "return NULL" case the straight
path, rather than the other way around. That seems more robust than
the compiler relying on knowledge that elog(ERROR) does not return.

OK, I updated the patches to do so and rebased. Those are now 0001 and
0002. For 0002, I have added more adapted comments at the top of
get_publication_name and get_subscription_name.

As far as format_type_extended() is concerned, IMO we've gone far enough
with the number of variants of format_type(). Making the function
public makes sense to me, but let's add a bits32 flags argument instead
of exposing the messy set of booleans. We can add compatibility
wrappers for the flag combinations most used in core code, and maybe
take the opportunity phase out the uncommon ones.

OK, I was a bit hesitant to propose that without more input, so I
definitely agree with this API interface. I have tackled that in 0003,
with the following changes:
- let's get rid of format_type_with_typemod_qualified. This is only
used by postgres_fdw in one place.
- format_type_be_qualified is also rather localized, but I have kept
it. Perhaps this could be nuked as well. Input is welcome.
- let's keep format_type_be and format_type_with_typemod. Those are
largely more spread in the core code, so I don't think that we need to
invade things more than necessary.

Attached is a rebased and updated patch set. I have also reworked the
dance with elog calls and missing_ok to match with what you have already
committed.
--
Michael

Attachments:

0001-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From 50db60c497fedafbe7616bea10d2699be077f7dc Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 11:12:44 +0900
Subject: [PATCH 1/4] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object.
---
 contrib/dblink/dblink.c             |  2 +-
 contrib/file_fdw/file_fdw.c         |  4 ++--
 contrib/postgres_fdw/connection.c   |  4 ++--
 contrib/postgres_fdw/postgres_fdw.c |  8 ++++----
 doc/src/sgml/fdwhandler.sgml        | 10 ++++++++--
 src/backend/catalog/objectaddress.c | 12 ++++++------
 src/backend/commands/foreigncmds.c  | 14 ++++++++------
 src/backend/commands/tablecmds.c    |  8 ++++----
 src/backend/foreign/foreign.c       | 28 ++++++++++++++++++++++------
 src/include/foreign/foreign.h       |  4 ++--
 10 files changed, 59 insertions(+), 35 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index ae7e24ad08..a67febac6f 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2784,7 +2784,7 @@ get_connect_string(const char *servername)
 		Oid			userid = GetUserId();
 
 		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
+		fdw = GetForeignDataWrapper(fdwid, false);
 
 		/* Check permissions, user must have usage on the server. */
 		aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index cf0a3629bc..d837f977e8 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -357,8 +357,8 @@ fileGetOptions(Oid foreigntableid,
 	 * Simplify?)
 	 */
 	table = GetForeignTable(foreigntableid);
-	server = GetForeignServer(table->serverid);
-	wrapper = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(table->serverid, false);
+	wrapper = GetForeignDataWrapper(server->fdwid, false);
 
 	options = NIL;
 	options = list_concat(options, wrapper->options);
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 00c926b983..3503595b7b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -182,7 +182,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
 	 */
 	if (entry->conn == NULL)
 	{
-		ForeignServer *server = GetForeignServer(user->serverid);
+		ForeignServer *server = GetForeignServer(user->serverid, false);
 
 		/* Reset all transient state fields, to be sure all are clean */
 		entry->xact_depth = 0;
@@ -1003,7 +1003,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
 	umform = (Form_pg_user_mapping) GETSTRUCT(tup);
-	server = GetForeignServer(umform->umserver);
+	server = GetForeignServer(umform->umserver, false);
 	ReleaseSysCache(tup);
 
 	ereport(ERROR,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index d37180ae10..931345f687 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -520,7 +520,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/* Look up foreign-table catalog info. */
 	fpinfo->table = GetForeignTable(foreigntableid);
-	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+	fpinfo->server = GetForeignServer(fpinfo->table->serverid, false);
 
 	/*
 	 * Extract user-settable option values.  Note that per-table setting of
@@ -2053,7 +2053,7 @@ postgresIsForeignRelUpdatable(Relation rel)
 	updatable = true;
 
 	table = GetForeignTable(RelationGetRelid(rel));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 
 	foreach(lc, server->options)
 	{
@@ -3998,7 +3998,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	 * owner, even if the ANALYZE was started by some other user.
 	 */
 	table = GetForeignTable(RelationGetRelid(relation));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false);
 
@@ -4221,7 +4221,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	server = GetForeignServer(serverOid);
+	server = GetForeignServer(serverOid, false);
 	mapping = GetUserMapping(GetUserId(), server->serverid);
 	conn = GetConnection(mapping, false);
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 0ed3a47233..af6b5822f6 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1333,25 +1333,31 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid);
+GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignDataWrapper</structname>
      object for the foreign-data wrapper with the given OID.  A
      <structname>ForeignDataWrapper</structname> object contains properties
      of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign-data wrapper.
     </para>
 
     <para>
 <programlisting>
 ForeignServer *
-GetForeignServer(Oid serverid);
+GetForeignServer(Oid serverid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignServer</structname> object
      for the foreign server with the given OID.  A
      <structname>ForeignServer</structname> object contains properties
      of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
     </para>
 
     <para>
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index b4c2467710..845489779b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3204,7 +3204,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
@@ -3213,7 +3213,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
@@ -3233,7 +3233,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
@@ -4711,7 +4711,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
 				if (objname)
 					*objname = list_make1(pstrdup(fdw->fdwname));
@@ -4722,7 +4722,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(srv->servername));
 				if (objname)
@@ -4745,7 +4745,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 5c53aeeaeb..645e2efb74 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -368,7 +368,8 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
 			if (aclresult != ACLCHECK_OK)
 			{
-				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw,
+																false);
 
 				aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname);
 			}
@@ -1033,7 +1034,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	if (stmt->options)
 	{
-		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw,
+														false);
 		Datum		datum;
 		bool		isnull;
 
@@ -1187,7 +1189,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 							stmt->servername)));
 	}
 
-	fdw = GetForeignDataWrapper(srv->fdwid);
+	fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_user_mapping.
@@ -1299,7 +1301,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 		 * Process the options.
 		 */
 
-		fdw = GetForeignDataWrapper(srv->fdwid);
+		fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
 								tp,
@@ -1479,7 +1481,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, server->servername);
 
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_foreign_table.
@@ -1542,7 +1544,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 	(void) LookupCreationNamespace(stmt->local_schema);
 
 	/* Get the FDW and check it supports IMPORT */
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 	if (!OidIsValid(fdw->fdwhandler))
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 89454d8e80..05b29eb477 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9548,8 +9548,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(fttableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(fttableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	heap_close(ftrel, AccessShareLock);
 	ReleaseSysCache(tuple);
@@ -12451,8 +12451,8 @@ ATExecGenericOptions(Relation rel, List *options)
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(tableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(tableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	memset(repl_val, 0, sizeof(repl_val));
 	memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index e7fd507fa5..df672c97c5 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -32,7 +32,7 @@
  * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
  */
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid)
+GetForeignDataWrapper(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +43,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -82,7 +86,11 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
 	if (!OidIsValid(fdwId))
 		return NULL;
 
-	return GetForeignDataWrapper(fdwId);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignDataWrapper(fdwId, false);
 }
 
 
@@ -90,7 +98,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  * GetForeignServer - look up the foreign server definition.
  */
 ForeignServer *
-GetForeignServer(Oid serverid)
+GetForeignServer(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +109,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
@@ -152,7 +164,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 	if (!OidIsValid(serverid))
 		return NULL;
 
-	return GetForeignServer(serverid);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignServer(serverid, false);
 }
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..df969d04ea 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -69,10 +69,10 @@ typedef struct ForeignTable
 } ForeignTable;
 
 
-extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServer(Oid serverid, bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
-extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.16.1

0002-Refactor-routines-for-subscription-and-publication-l.patchtext/x-diff; charset=us-asciiDownload
From 948673001497d2df156a4c01ea8fbce623901b00 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 11:17:56 +0900
Subject: [PATCH 2/4] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  | 11 +++++++++--
 src/backend/catalog/pg_subscription.c | 11 +++++++++--
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 845489779b..0844ea72eb 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3385,7 +3385,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3402,7 +3403,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3413,7 +3414,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4911,7 +4912,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4932,7 +4933,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4951,7 +4952,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index b4a5f48b4e..102a09579a 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -417,9 +417,12 @@ get_publication_oid(const char *pubname, bool missing_ok)
 
 /*
  * get_publication_name - given a publication Oid, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -428,7 +431,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+		return NULL;
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8e16d3b7bc..89d5520e4e 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -179,9 +179,12 @@ get_subscription_oid(const char *subname, bool missing_ok)
 
 /*
  * get_subscription_name - given a subscription OID, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +193,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for subscription %u", subid);
+		return NULL;
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 7bdc634cf3..bd960de835 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -97,7 +97,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 46d0b48232..307893b1f1 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -89,7 +89,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.16.1

0003-Refactor-format_type-APIs-to-be-more-modular.patchtext/x-diff; charset=us-asciiDownload
From bb58da8003a276ece047f5c9b1acab3966f09167 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 12:13:39 +0900
Subject: [PATCH 3/4] Refactor format_type APIs to be more modular

As of now, format type names can be generated using multiple APIs which
share much:
- format_type_be, which is used mainly for error messages.
- format_type_be_qualified, which qualifies the type name.
- format_type_with_typemod, which allows a non-default typemod to be
defined.
- format_type_with_typemod_qualified, which allows a non default typemod
to be specified and qualifies the type name.  This is used only by
postgres_fdw.

This patch removes format_type_with_typemod_qualified and makes
format_type_internal publicly available with a set of flags that callers
can use to control how a type name is generated.
format_type_be_qualified is also localized, and could be plugged off,
but this is left for future consideration.
---
 contrib/postgres_fdw/deparse.c      | 10 ++++---
 src/backend/utils/adt/format_type.c | 60 ++++++++++++++++++-------------------
 src/include/utils/builtins.h        | 11 +++++--
 3 files changed, 44 insertions(+), 37 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index f4b38c65ac..02894a7e35 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -854,10 +854,12 @@ foreign_expr_walker(Node *node,
 static char *
 deparse_type_name(Oid type_oid, int32 typemod)
 {
-	if (is_builtin(type_oid))
-		return format_type_with_typemod(type_oid, typemod);
-	else
-		return format_type_with_typemod_qualified(type_oid, typemod);
+	uint8 flags = FORMAT_TYPE_TYPEMOD_GIVEN;
+
+	if (!is_builtin(type_oid))
+		flags |= FORMAT_TYPE_FORCE_QUALIFY;
+
+	return format_type_extended(type_oid, typemod, flags);
 }
 
 /*
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index de3da3607a..58eb220ff8 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -28,9 +28,6 @@
 
 #define MAX_INT32_LEN 11
 
-static char *format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify);
 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
 
 
@@ -74,11 +71,12 @@ format_type(PG_FUNCTION_ARGS)
 	type_oid = PG_GETARG_OID(0);
 
 	if (PG_ARGISNULL(1))
-		result = format_type_internal(type_oid, -1, false, true, false);
+		result = format_type_extended(type_oid, -1, FORMAT_TYPE_ALLOW_INVALID);
 	else
 	{
 		typemod = PG_GETARG_INT32(1);
-		result = format_type_internal(type_oid, typemod, true, true, false);
+		result = format_type_extended(type_oid, typemod,
+					  FORMAT_TYPE_TYPEMOD_GIVEN | FORMAT_TYPE_ALLOW_INVALID);
 	}
 
 	PG_RETURN_TEXT_P(cstring_to_text(result));
@@ -93,7 +91,7 @@ format_type(PG_FUNCTION_ARGS)
 char *
 format_type_be(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, false);
+	return format_type_extended(type_oid, -1, 0);
 }
 
 /*
@@ -103,7 +101,7 @@ format_type_be(Oid type_oid)
 char *
 format_type_be_qualified(Oid type_oid)
 {
-	return format_type_internal(type_oid, -1, false, false, true);
+	return format_type_extended(type_oid, -1, FORMAT_TYPE_FORCE_QUALIFY);
 }
 
 /*
@@ -112,41 +110,40 @@ format_type_be_qualified(Oid type_oid)
 char *
 format_type_with_typemod(Oid type_oid, int32 typemod)
 {
-	return format_type_internal(type_oid, typemod, true, false, false);
+	return format_type_extended(type_oid, typemod, FORMAT_TYPE_TYPEMOD_GIVEN);
 }
 
 /*
- * This version allows a nondefault typemod to be specified, and forces
- * qualification of normal type names.
+ * format_type_extended
+ *
+ * Workhouse to generate a type name, controlled by the following set of
+ * options:
+ * - FORMAT_TYPE_TYPEMOD_GIVEN, which allows a non default typemod to be
+ *   specified.
+ * - FORMAT_TYPE_ALLOW_INVALID, which does not generate an error for an
+ *   invalid type and returns a result back to the caller.
+ * - FORMAT_TYPE_FORCE_QUALIFY, which forces the qualification of normal
+ *   type names.
  */
 char *
-format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
-{
-	return format_type_internal(type_oid, typemod, true, false, true);
-}
-
-/*
- * Common workhorse.
- */
-static char *
-format_type_internal(Oid type_oid, int32 typemod,
-					 bool typemod_given, bool allow_invalid,
-					 bool force_qualify)
+format_type_extended(Oid type_oid, int32 typemod,
+					 uint8 flags)
 {
-	bool		with_typemod = typemod_given && (typemod >= 0);
+	bool		with_typemod = (flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0 &&
+							   (typemod >= 0);
 	HeapTuple	tuple;
 	Form_pg_type typeform;
 	Oid			array_base_type;
 	bool		is_array;
 	char	   *buf;
 
-	if (type_oid == InvalidOid && allow_invalid)
+	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 		return pstrdup("-");
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if (allow_invalid)
+		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -170,7 +167,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if (allow_invalid)
+			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -200,7 +197,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 		case BITOID:
 			if (with_typemod)
 				buf = printTypmod("bit", typemod, typeform->typmodout);
-			else if (typemod_given)
+			else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
 			{
 				/*
 				 * bit with typmod -1 is not the same as BIT, which means
@@ -219,7 +216,7 @@ format_type_internal(Oid type_oid, int32 typemod,
 		case BPCHAROID:
 			if (with_typemod)
 				buf = printTypmod("character", typemod, typeform->typmodout);
-			else if (typemod_given)
+			else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
 			{
 				/*
 				 * bpchar with typmod -1 is not the same as CHARACTER, which
@@ -319,7 +316,8 @@ format_type_internal(Oid type_oid, int32 typemod,
 		char	   *nspname;
 		char	   *typname;
 
-		if (!force_qualify && TypeIsVisible(type_oid))
+		if ((flags & FORMAT_TYPE_FORCE_QUALIFY) == 0 &&
+			TypeIsVisible(type_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name_or_temp(typeform->typnamespace);
@@ -437,8 +435,8 @@ oidvectortypes(PG_FUNCTION_ARGS)
 
 	for (num = 0; num < numargs; num++)
 	{
-		char	   *typename = format_type_internal(oidArray->values[num], -1,
-													false, true, false);
+		char	   *typename = format_type_extended(oidArray->values[num], -1,
+													FORMAT_TYPE_ALLOW_INVALID);
 		size_t		slen = strlen(typename);
 
 		if (left < (slen + 2))
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 8bb57c5829..27aa67c3fa 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -112,11 +112,18 @@ extern void clean_ipv6_addr(int addr_family, char *addr);
 extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 
 /* format_type.c */
+extern char *format_type_extended(Oid type_oid, int32 typemod, uint8 flags);
+extern int32 type_maximum_size(Oid type_oid, int32 typemod);
+
+/* Control flags for format_type_extended */
+#define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
+#define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
+#define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+
+/* Those declarations are deprecated, and kept for compatibility */
 extern char *format_type_be(Oid type_oid);
 extern char *format_type_be_qualified(Oid type_oid);
 extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
-extern char *format_type_with_typemod_qualified(Oid type_oid, int32 typemod);
-extern int32 type_maximum_size(Oid type_oid, int32 typemod);
 
 /* quote.c */
 extern char *quote_literal_cstr(const char *rawstr);
-- 
2.16.1

0004-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From bb2da6d10650d7499acbe04e2719659da7b445af Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 12 Jan 2018 10:55:12 +0900
Subject: [PATCH 4/4] Eliminate user-visible cache lookup errors for objaddr 
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 997 +++++++++++++++++++--------
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  22 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 ++++
 19 files changed, 1611 insertions(+), 373 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index c1fa320eb4..709d89e4d2 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 7554017923..ee591ca10b 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -372,7 +372,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -543,7 +543,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 640ff09a7b..3f256f3917 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17547,7 +17547,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17563,7 +17564,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17578,6 +17580,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b7e39af7a2..4afbdf6d99 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -608,11 +608,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -692,11 +692,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -788,12 +788,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -887,7 +887,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -910,7 +910,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -968,7 +969,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 0844ea72eb..db9f0d5d4b 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -754,14 +754,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1628,7 +1634,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1659,7 +1665,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2667,10 +2673,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2679,22 +2687,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  FORMAT_TYPE_ALLOW_INVALID));
 			break;
 
 		case OCLASS_CAST:
@@ -2717,15 +2731,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2739,13 +2757,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2757,8 +2780,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2766,7 +2794,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2788,8 +2816,12 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2817,18 +2849,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2836,9 +2872,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2846,6 +2888,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2861,8 +2904,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2889,7 +2937,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2899,8 +2947,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2929,28 +2982,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2979,28 +3036,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3027,15 +3088,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3062,15 +3127,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3083,8 +3152,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3097,8 +3170,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3116,8 +3193,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3131,8 +3212,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3146,8 +3231,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3161,8 +3250,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3171,8 +3264,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3182,8 +3278,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3194,8 +3294,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3204,8 +3308,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3213,8 +3318,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3229,8 +3335,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -3268,8 +3379,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3326,8 +3444,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3339,8 +3461,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3367,15 +3493,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3384,9 +3515,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3399,8 +3532,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3413,8 +3550,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3426,8 +3565,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3445,6 +3588,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3460,14 +3607,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3477,7 +3624,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3538,7 +3689,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3548,7 +3699,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3592,7 +3747,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3607,6 +3766,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3680,8 +3841,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3704,8 +3871,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3722,6 +3895,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3749,23 +3923,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
+	else
+	{
+		pfree(identity);
 
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3779,7 +3967,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3789,11 +3977,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3809,7 +3999,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3946,6 +4137,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3953,7 +4148,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3961,7 +4157,12 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		Assert(buffer->len == 0);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4008,7 +4209,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4017,7 +4218,13 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4035,7 +4242,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4043,7 +4251,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->proisagg)
@@ -4060,12 +4272,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4074,11 +4287,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4100,31 +4315,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_ALLOW_INVALID | FORMAT_TYPE_FORCE_QUALIFY);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4142,8 +4367,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4170,8 +4401,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4192,15 +4427,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4215,7 +4455,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4234,8 +4475,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4270,19 +4515,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
 
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
+					elog(ERROR, "could not find tuple for attrdef %u",
+						 object->objectId);
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4297,8 +4547,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4315,10 +4569,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4332,8 +4587,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4359,7 +4618,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4368,8 +4628,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4401,13 +4665,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4455,13 +4727,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4495,14 +4775,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4521,14 +4807,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4542,8 +4834,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4560,8 +4856,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4583,8 +4883,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4606,8 +4910,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4629,8 +4937,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4652,8 +4964,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4670,7 +4986,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4684,8 +5002,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4699,8 +5021,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4712,10 +5038,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4723,11 +5052,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4742,8 +5074,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -4790,8 +5126,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4853,8 +5197,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4873,8 +5221,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4893,14 +5245,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4912,11 +5270,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4929,8 +5290,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4940,7 +5305,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4952,11 +5318,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4973,8 +5342,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5000,20 +5375,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5023,7 +5398,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5052,7 +5431,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5061,7 +5441,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 549c7ea51d..e5faf5e0f5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1399,10 +1399,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2060,8 +2061,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2e4538146d..a4c622ee11 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 05b29eb477..7dd97d0883 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9250,7 +9250,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9298,7 +9298,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9317,7 +9317,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9335,7 +9335,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9396,7 +9396,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..623346aafc 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index bfd9d54c11..d74715eff7 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,3 +492,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 13 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 55faa71edf..0772173775 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -213,3 +213,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.16.1

#19Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Michael Paquier (#18)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

Michael Paquier wrote:

As far as format_type_extended() is concerned, IMO we've gone far enough
with the number of variants of format_type(). Making the function
public makes sense to me, but let's add a bits32 flags argument instead
of exposing the messy set of booleans. We can add compatibility
wrappers for the flag combinations most used in core code, and maybe
take the opportunity phase out the uncommon ones.

OK, I was a bit hesitant to propose that without more input, so I
definitely agree with this API interface. I have tackled that in 0003,
with the following changes:
- let's get rid of format_type_with_typemod_qualified. This is only
used by postgres_fdw in one place.
- format_type_be_qualified is also rather localized, but I have kept
it. Perhaps this could be nuked as well. Input is welcome.
- let's keep format_type_be and format_type_with_typemod. Those are
largely more spread in the core code, so I don't think that we need to
invade things more than necessary.

Pushed 0003. Maybe we can get rid of format_type_be_qualified too, but
I didn't care too much about it either; it's not a big deal I think.

What interested me more was whether we could get rid of the
FORMAT_TYPE_TYPEMOD_GIVEN flag, but ended up deciding not to pursue that
as a phenomenal waste of time. Here are some references in case you
care.

/messages/by-id/200111101659.fAAGxKX06044@postgresql.org
https://git.postgresql.org/pg/commitdiff/a585c20d12d0e22befc8308e9f8ccb6f54a5df69

Thanks

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#20Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#19)
Re: [HACKERS] Cache lookup errors with functions manipulation object addresses

On Sat, Feb 17, 2018 at 07:17:24PM -0300, Alvaro Herrera wrote:

Pushed 0003.

Thanks.

Maybe we can get rid of format_type_be_qualified too, but I didn't
care too much about it either; it's not a big deal I think.

Agreed. There are 16 callers spread in objectaddress.c and regproc.c,
this would generate some diffs. If there are extra opinions later on,
we could always revisit that. The new API is modular enough anyway.

What interested me more was whether we could get rid of the
FORMAT_TYPE_TYPEMOD_GIVEN flag, but ended up deciding not to pursue that
as a phenomenal waste of time. Here are some references in case you
care.

/messages/by-id/200111101659.fAAGxKX06044@postgresql.org
https://git.postgresql.org/pg/commitdiff/a585c20d12d0e22befc8308e9f8ccb6f54a5df69

Thanks for the threads, I didn't know about them. I thought as well
about trying to remove FORMAT_TYPE_TYPEMOD_GIVEN but avoided to do so,
so as not to break things the way they should be for a long time as this
code is as it is now for at least as long as I am working on Postgres.
I didn't check the git history to see the logic behind the code though,
which I really should have done. So thanks for the references.
--
Michael

#21Aleksander Alekseev
a.alekseev@postgrespro.ru
In reply to: Michael Paquier (#20)
Re: Cache lookup errors with functions manipulation object addresses

The following review has been posted through the commitfest application:
make installcheck-world: not tested
Implements feature: not tested
Spec compliant: not tested
Documentation: not tested

Hello Michael,

It looks like that this patch doesn't apply anymore: http://commitfest.cputube.org/

The new status of this patch is: Waiting on Author

#22Michael Paquier
michael@paquier.xyz
In reply to: Aleksander Alekseev (#21)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Mon, Mar 05, 2018 at 12:57:38PM +0000, Aleksander Alekseev wrote:

It looks like that this patch doesn't apply anymore: http://commitfest.cputube.org/

The new status of this patch is: Waiting on Author

Yes, this happens because patch 0003 from the last series has been
committed as a26116c6. Attached is a rebased set, though the patches
have no actual changes as those are able to apply correctly.
--
Michael

Attachments:

0001-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From 7fedf5f334acf0bddcf2f5672df368302ceb698b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 11:12:44 +0900
Subject: [PATCH 1/3] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object.
---
 contrib/dblink/dblink.c             |  2 +-
 contrib/file_fdw/file_fdw.c         |  4 ++--
 contrib/postgres_fdw/connection.c   |  4 ++--
 contrib/postgres_fdw/postgres_fdw.c |  8 ++++----
 doc/src/sgml/fdwhandler.sgml        | 10 ++++++++--
 src/backend/catalog/objectaddress.c | 12 ++++++------
 src/backend/commands/foreigncmds.c  | 14 ++++++++------
 src/backend/commands/tablecmds.c    |  8 ++++----
 src/backend/foreign/foreign.c       | 28 ++++++++++++++++++++++------
 src/include/foreign/foreign.h       |  4 ++--
 10 files changed, 59 insertions(+), 35 deletions(-)

diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index ae7e24ad08..a67febac6f 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2784,7 +2784,7 @@ get_connect_string(const char *servername)
 		Oid			userid = GetUserId();
 
 		user_mapping = GetUserMapping(userid, serverid);
-		fdw = GetForeignDataWrapper(fdwid);
+		fdw = GetForeignDataWrapper(fdwid, false);
 
 		/* Check permissions, user must have usage on the server. */
 		aclresult = pg_foreign_server_aclcheck(serverid, userid, ACL_USAGE);
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index cf0a3629bc..d837f977e8 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -357,8 +357,8 @@ fileGetOptions(Oid foreigntableid,
 	 * Simplify?)
 	 */
 	table = GetForeignTable(foreigntableid);
-	server = GetForeignServer(table->serverid);
-	wrapper = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(table->serverid, false);
+	wrapper = GetForeignDataWrapper(server->fdwid, false);
 
 	options = NIL;
 	options = list_concat(options, wrapper->options);
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 00c926b983..3503595b7b 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -182,7 +182,7 @@ GetConnection(UserMapping *user, bool will_prep_stmt)
 	 */
 	if (entry->conn == NULL)
 	{
-		ForeignServer *server = GetForeignServer(user->serverid);
+		ForeignServer *server = GetForeignServer(user->serverid, false);
 
 		/* Reset all transient state fields, to be sure all are clean */
 		entry->xact_depth = 0;
@@ -1003,7 +1003,7 @@ pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry)
 	if (!HeapTupleIsValid(tup))
 		elog(ERROR, "cache lookup failed for user mapping %u", entry->key);
 	umform = (Form_pg_user_mapping) GETSTRUCT(tup);
-	server = GetForeignServer(umform->umserver);
+	server = GetForeignServer(umform->umserver, false);
 	ReleaseSysCache(tup);
 
 	ereport(ERROR,
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index e8a0d5482a..590469fe9e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -520,7 +520,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/* Look up foreign-table catalog info. */
 	fpinfo->table = GetForeignTable(foreigntableid);
-	fpinfo->server = GetForeignServer(fpinfo->table->serverid);
+	fpinfo->server = GetForeignServer(fpinfo->table->serverid, false);
 
 	/*
 	 * Extract user-settable option values.  Note that per-table setting of
@@ -2053,7 +2053,7 @@ postgresIsForeignRelUpdatable(Relation rel)
 	updatable = true;
 
 	table = GetForeignTable(RelationGetRelid(rel));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 
 	foreach(lc, server->options)
 	{
@@ -3998,7 +3998,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	 * owner, even if the ANALYZE was started by some other user.
 	 */
 	table = GetForeignTable(RelationGetRelid(relation));
-	server = GetForeignServer(table->serverid);
+	server = GetForeignServer(table->serverid, false);
 	user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
 	conn = GetConnection(user, false);
 
@@ -4221,7 +4221,7 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	server = GetForeignServer(serverOid);
+	server = GetForeignServer(serverOid, false);
 	mapping = GetUserMapping(GetUserId(), server->serverid);
 	conn = GetConnection(mapping, false);
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 0ed3a47233..af6b5822f6 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1333,25 +1333,31 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid);
+GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignDataWrapper</structname>
      object for the foreign-data wrapper with the given OID.  A
      <structname>ForeignDataWrapper</structname> object contains properties
      of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign-data wrapper.
     </para>
 
     <para>
 <programlisting>
 ForeignServer *
-GetForeignServer(Oid serverid);
+GetForeignServer(Oid serverid, bool missing_ok);
 </programlisting>
 
      This function returns a <structname>ForeignServer</structname> object
      for the foreign server with the given OID.  A
      <structname>ForeignServer</structname> object contains properties
      of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
     </para>
 
     <para>
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 119297b33a..a748df7a3a 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3206,7 +3206,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
@@ -3215,7 +3215,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
@@ -3235,7 +3235,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
@@ -4713,7 +4713,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
+				fdw = GetForeignDataWrapper(object->objectId, false);
 				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
 				if (objname)
 					*objname = list_make1(pstrdup(fdw->fdwname));
@@ -4724,7 +4724,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
+				srv = GetForeignServer(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(srv->servername));
 				if (objname)
@@ -4747,7 +4747,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
-				srv = GetForeignServer(umform->umserver);
+				srv = GetForeignServer(umform->umserver, false);
 
 				ReleaseSysCache(tup);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 5c53aeeaeb..645e2efb74 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -368,7 +368,8 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
 			aclresult = pg_foreign_data_wrapper_aclcheck(form->srvfdw, newOwnerId, ACL_USAGE);
 			if (aclresult != ACLCHECK_OK)
 			{
-				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw);
+				ForeignDataWrapper *fdw = GetForeignDataWrapper(form->srvfdw,
+																false);
 
 				aclcheck_error(aclresult, OBJECT_FDW, fdw->fdwname);
 			}
@@ -1033,7 +1034,8 @@ AlterForeignServer(AlterForeignServerStmt *stmt)
 
 	if (stmt->options)
 	{
-		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw);
+		ForeignDataWrapper *fdw = GetForeignDataWrapper(srvForm->srvfdw,
+														false);
 		Datum		datum;
 		bool		isnull;
 
@@ -1187,7 +1189,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 							stmt->servername)));
 	}
 
-	fdw = GetForeignDataWrapper(srv->fdwid);
+	fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_user_mapping.
@@ -1299,7 +1301,7 @@ AlterUserMapping(AlterUserMappingStmt *stmt)
 		 * Process the options.
 		 */
 
-		fdw = GetForeignDataWrapper(srv->fdwid);
+		fdw = GetForeignDataWrapper(srv->fdwid, false);
 
 		datum = SysCacheGetAttr(USERMAPPINGUSERSERVER,
 								tp,
@@ -1479,7 +1481,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, server->servername);
 
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	/*
 	 * Insert tuple into pg_foreign_table.
@@ -1542,7 +1544,7 @@ ImportForeignSchema(ImportForeignSchemaStmt *stmt)
 	(void) LookupCreationNamespace(stmt->local_schema);
 
 	/* Get the FDW and check it supports IMPORT */
-	fdw = GetForeignDataWrapper(server->fdwid);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 	if (!OidIsValid(fdw->fdwhandler))
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 74e020bffc..d472c94030 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9563,8 +9563,8 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	fttableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(fttableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(fttableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	heap_close(ftrel, AccessShareLock);
 	ReleaseSysCache(tuple);
@@ -12466,8 +12466,8 @@ ATExecGenericOptions(Relation rel, List *options)
 				 errmsg("foreign table \"%s\" does not exist",
 						RelationGetRelationName(rel))));
 	tableform = (Form_pg_foreign_table) GETSTRUCT(tuple);
-	server = GetForeignServer(tableform->ftserver);
-	fdw = GetForeignDataWrapper(server->fdwid);
+	server = GetForeignServer(tableform->ftserver, false);
+	fdw = GetForeignDataWrapper(server->fdwid, false);
 
 	memset(repl_val, 0, sizeof(repl_val));
 	memset(repl_null, false, sizeof(repl_null));
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index eac78a5d31..07b5857bfa 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -32,7 +32,7 @@
  * GetForeignDataWrapper -	look up the foreign-data wrapper by OID.
  */
 ForeignDataWrapper *
-GetForeignDataWrapper(Oid fdwid)
+GetForeignDataWrapper(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +43,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -82,7 +86,11 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
 	if (!OidIsValid(fdwId))
 		return NULL;
 
-	return GetForeignDataWrapper(fdwId);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignDataWrapper(fdwId, false);
 }
 
 
@@ -90,7 +98,7 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  * GetForeignServer - look up the foreign server definition.
  */
 ForeignServer *
-GetForeignServer(Oid serverid)
+GetForeignServer(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +109,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
@@ -152,7 +164,11 @@ GetForeignServerByName(const char *srvname, bool missing_ok)
 	if (!OidIsValid(serverid))
 		return NULL;
 
-	return GetForeignServer(serverid);
+	/*
+	 * missing_ok set to true makes no sense here as a lookup has already
+	 * happened.
+	 */
+	return GetForeignServer(serverid, false);
 }
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..df969d04ea 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -69,10 +69,10 @@ typedef struct ForeignTable
 } ForeignTable;
 
 
-extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServer(Oid serverid, bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
-extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid, bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.16.2

0002-Refactor-routines-for-subscription-and-publication-l.patchtext/x-diff; charset=us-asciiDownload
From 656b2ddc55a18ac39a56ea73f8616bb67da8218a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 11:17:56 +0900
Subject: [PATCH 2/3] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  | 11 +++++++++--
 src/backend/catalog/pg_subscription.c | 11 +++++++++--
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index a748df7a3a..4925601a24 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3387,7 +3387,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3404,7 +3405,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3415,7 +3416,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4913,7 +4914,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4934,7 +4935,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4953,7 +4954,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ba18258ebb..06867edc0c 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -426,9 +426,12 @@ get_publication_oid(const char *pubname, bool missing_ok)
 
 /*
  * get_publication_name - given a publication Oid, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -437,7 +440,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+		return NULL;
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8e16d3b7bc..89d5520e4e 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -179,9 +179,12 @@ get_subscription_oid(const char *subname, bool missing_ok)
 
 /*
  * get_subscription_name - given a subscription OID, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +193,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for subscription %u", subid);
+		return NULL;
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 37e77b8be7..92721348c7 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -98,7 +98,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 46d0b48232..307893b1f1 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -89,7 +89,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.16.2

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From 9aa60a457f836987e8425a3fb2cdca9cb8f4eef3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 12 Jan 2018 10:55:12 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr 
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 999 +++++++++++++++++++--------
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  22 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 ++++
 19 files changed, 1612 insertions(+), 374 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index c1fa320eb4..709d89e4d2 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 7554017923..ee591ca10b 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -372,7 +372,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -543,7 +543,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2f59af25a6..3442158350 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17618,7 +17618,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17634,7 +17635,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17649,6 +17651,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b7e39af7a2..4afbdf6d99 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -608,11 +608,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -692,11 +692,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -788,12 +788,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -887,7 +887,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -910,7 +910,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -968,7 +969,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 4925601a24..304525eeb3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -754,14 +754,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1630,7 +1636,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1661,7 +1667,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2669,10 +2675,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2681,22 +2689,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  FORMAT_TYPE_ALLOW_INVALID));
 			break;
 
 		case OCLASS_CAST:
@@ -2719,15 +2733,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2741,13 +2759,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2759,8 +2782,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2768,7 +2796,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2790,8 +2818,12 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2819,18 +2851,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
+
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for attrdef %u",
 						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
-
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
-
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2838,9 +2874,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2848,6 +2890,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2863,8 +2906,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2891,7 +2939,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2901,8 +2949,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2931,28 +2984,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2981,28 +3038,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3029,15 +3090,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3064,15 +3129,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3085,8 +3154,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3099,8 +3172,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3118,8 +3195,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3133,8 +3214,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3148,8 +3233,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3163,8 +3252,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3173,8 +3266,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3184,8 +3280,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3196,8 +3296,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3206,8 +3310,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3215,8 +3320,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3231,8 +3337,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -3270,8 +3381,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3328,8 +3446,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3341,8 +3463,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3369,15 +3495,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3386,9 +3517,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3401,8 +3534,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3415,8 +3552,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3428,8 +3567,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3447,6 +3590,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3462,14 +3609,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3479,7 +3626,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3540,7 +3691,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3550,7 +3701,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3594,7 +3749,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3609,6 +3768,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3682,8 +3843,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3706,8 +3873,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3724,6 +3897,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3751,23 +3925,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3781,7 +3969,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3791,11 +3979,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3811,7 +4001,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3948,6 +4139,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3955,7 +4150,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3963,7 +4159,12 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		Assert(buffer->len == 0);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4010,7 +4211,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4019,7 +4220,13 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4037,7 +4244,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4045,7 +4253,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4062,12 +4274,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4076,11 +4289,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4102,31 +4317,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_ALLOW_INVALID | FORMAT_TYPE_FORCE_QUALIFY);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4144,8 +4369,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4172,8 +4403,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4194,15 +4429,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4217,7 +4457,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4236,8 +4477,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4272,19 +4517,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
+
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for attrdef %u",
 						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
-
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
-
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4299,8 +4549,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4317,10 +4571,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4334,8 +4589,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4361,7 +4620,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4370,8 +4630,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4403,13 +4667,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4457,13 +4729,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4497,14 +4777,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4523,14 +4809,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4544,8 +4836,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4562,8 +4858,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4585,8 +4885,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4608,8 +4912,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4631,8 +4939,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4654,8 +4966,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4672,7 +4988,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4686,8 +5004,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4701,8 +5023,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4714,10 +5040,13 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId, false);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapper(object->objectId, missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4725,11 +5054,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServer(object->objectId, missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4744,8 +5076,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver, false);
@@ -4792,8 +5128,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4855,8 +5199,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4875,8 +5223,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4895,14 +5247,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4914,11 +5272,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4931,8 +5292,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4942,7 +5307,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4954,11 +5320,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4975,8 +5344,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5002,20 +5377,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5025,7 +5400,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5054,7 +5433,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5063,7 +5443,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 549c7ea51d..e5faf5e0f5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1399,10 +1399,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2060,8 +2061,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2e4538146d..a4c622ee11 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d472c94030..ad84a1ecab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9265,7 +9265,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9313,7 +9313,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9332,7 +9332,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9350,7 +9350,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9411,7 +9411,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..623346aafc 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index bfd9d54c11..d74715eff7 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,3 +492,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 13 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 55faa71edf..0772173775 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -213,3 +213,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.16.2

#23Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#22)
Re: Cache lookup errors with functions manipulation object addresses

On 2018-Mar-06, Michael Paquier wrote:

On Mon, Mar 05, 2018 at 12:57:38PM +0000, Aleksander Alekseev wrote:

It looks like that this patch doesn't apply anymore: http://commitfest.cputube.org/

The new status of this patch is: Waiting on Author

Yes, this happens because patch 0003 from the last series has been
committed as a26116c6. Attached is a rebased set, though the patches
have no actual changes as those are able to apply correctly.

I forgot to mention this earlier: I think external FDWs do not really
benefit from your 0001 (neither do most of the internal callsites of the
modified functions). I checked a bunch of source files and nowhere I
got a "Gee, I wish this returned InvalidOid if the object in question
didn't exist" feeling.

https://github.com/Kozea/Multicorn/blob/master/src/multicorn.c
https://github.com/tds-fdw/tds_fdw/blob/master/src/tds_fdw.c
https://github.com/pramsey/pgsql-ogr-fdw/blob/master/ogr_fdw.c
https://github.com/EnterpriseDB/mysql_fdw/blob/master/mysql_fdw.c
https://github.com/laurenz/oracle_fdw/blob/master/oracle_fdw.c

I think we're better off adding a new function and avoid changing the
signature of GetForeignServer et al. Or maybe rename the function and
keep the original name as a compatibility wrapper macro.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#24Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#23)
Re: Cache lookup errors with functions manipulation object addresses

On Mon, May 14, 2018 at 04:27:48PM -0400, Alvaro Herrera wrote:

I think we're better off adding a new function and avoid changing the
signature of GetForeignServer et al. Or maybe rename the function and
keep the original name as a compatibility wrapper macro.

On the other hand, if we make the change visible because of a
compilation failures, then modules would become aware of the problem and
react? I would not expect modules to set missing_ok to true anyway as
they expect those objects to exist, so I can live with a new function.
What about naming those GetForeignServerExtended and
GetForeignDataWrapperExtended?
--
Michael

#25Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#24)
Re: Cache lookup errors with functions manipulation object addresses

On 2018-May-15, Michael Paquier wrote:

On Mon, May 14, 2018 at 04:27:48PM -0400, Alvaro Herrera wrote:

I think we're better off adding a new function and avoid changing the
signature of GetForeignServer et al. Or maybe rename the function and
keep the original name as a compatibility wrapper macro.

On the other hand, if we make the change visible because of a
compilation failures, then modules would become aware of the problem and
react?

Presumably, if you invoke a FDW and its handler finds
that the ForeignServer doesn't exist, what is it to do other than raise
an error? I can't see that there's any possible improvement.

So, I don't know -- if the reaction is to add a #ifdef for pg version
that adds a second argument passed always false, then we haven't won
anything.

I would not expect modules to set missing_ok to true anyway as they
expect those objects to exist, so I can live with a new function.

Exactly.

What about naming those GetForeignServerExtended and
GetForeignDataWrapperExtended?

WFM.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#26Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#25)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Mon, May 14, 2018 at 06:32:52PM -0400, Alvaro Herrera wrote:

So, I don't know -- if the reaction is to add a #ifdef for pg version
that adds a second argument passed always false, then we haven't won
anything.

Yeah, that improves nothing..

What about naming those GetForeignServerExtended and
GetForeignDataWrapperExtended?

WFM.

Okay, I have done so in the updated set attached. I have added some
documentation as well in fdwhandler.sgml about those two new things.
That's too late for v11 of course, so let's them sit until the time
comes.
--
Michael

Attachments:

0001-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From be6f8c7b986ea283e1f346b18b1c3116d1956772 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 11:12:44 +0900
Subject: [PATCH 1/3] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object. This is added in a set of new routines to not impact
unnecessrily any FPW plugins.
---
 doc/src/sgml/fdwhandler.sgml  | 30 +++++++++++++++++++++++++++++
 src/backend/foreign/foreign.c | 36 +++++++++++++++++++++++++++++++++--
 src/include/foreign/foreign.h |  4 ++++
 3 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 7b758bdf09..bfc538f249 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1404,6 +1404,21 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname>
+     object for the foreign-data wrapper with the given OID.  A
+     <structname>ForeignDataWrapper</structname> object contains properties
+     of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     FDW.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid);
 </programlisting>
 
@@ -1416,6 +1431,21 @@ GetForeignDataWrapper(Oid fdwid);
     <para>
 <programlisting>
 ForeignServer *
+GetForeignServerExtended(Oid serverid, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object
+     for the foreign server with the given OID.  A
+     <structname>ForeignServer</structname> object contains properties
+     of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
 GetForeignServer(Oid serverid);
 </programlisting>
 
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index eac78a5d31..01b5175e71 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -33,6 +33,18 @@
  */
 ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid)
+{
+	return GetForeignDataWrapperExtended(fdwid, false);
+}
+
+
+/*
+ * GetForeignDataWrapperExtended -	look up the foreign-data wrapper
+ * by OID. If missing_ok is true, return NULL if the object cannot be
+ * found instead of raising an error.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +55,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -91,6 +107,18 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  */
 ForeignServer *
 GetForeignServer(Oid serverid)
+{
+	return GetForeignServerExtended(serverid, false);
+}
+
+
+/*
+ * GetForeignServerExtended - look up the foreign server definition. If
+ * missing_ok is true, return NULL if the object cannot be found instead
+ * of raising an error.
+ */
+ForeignServer *
+GetForeignServerExtended(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +129,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..5cc89e967c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -70,9 +70,13 @@ typedef struct ForeignTable
 
 
 extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServerExtended(Oid serverid,
+							bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
 extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapperExtended(Oid fdwid,
+							bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.17.0

0002-Refactor-routines-for-subscription-and-publication-l.patchtext/x-diff; charset=us-asciiDownload
From 2d5009b0c35606988d19c2567a14f0e413af970b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 13 Feb 2018 11:17:56 +0900
Subject: [PATCH 2/3] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  | 11 +++++++++--
 src/backend/catalog/pg_subscription.c | 11 +++++++++--
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d371c47842..644cb64c0c 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3387,7 +3387,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3404,7 +3405,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("publication table %s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -3415,7 +3416,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -4913,7 +4914,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -4934,7 +4935,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				appendStringInfo(&buffer, _("%s in publication %s"),
 								 get_rel_name(prform->prrelid), pubname);
@@ -4953,7 +4954,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ec3bd1d22d..3ecf6d57bf 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -427,9 +427,12 @@ get_publication_oid(const char *pubname, bool missing_ok)
 
 /*
  * get_publication_name - given a publication Oid, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -438,7 +441,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+		return NULL;
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8705d8b1d3..f891ff8054 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -179,9 +179,12 @@ get_subscription_oid(const char *subname, bool missing_ok)
 
 /*
  * get_subscription_name - given a subscription OID, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +193,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for subscription %u", subid);
+		return NULL;
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 83c59cb10d..a5d5570f76 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -88,7 +88,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 82dd6faf23..e4dc771cf5 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -80,7 +80,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.17.0

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From 072a5396b35c8a7895d80edb9757a01553af724e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 12 Jan 2018 10:55:12 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr 
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |    6 +-
 contrib/sepgsql/dml.c                        |    4 +-
 contrib/sepgsql/label.c                      |    4 +-
 contrib/sepgsql/proc.c                       |   14 +-
 contrib/sepgsql/relation.c                   |   20 +-
 contrib/sepgsql/schema.c                     |    6 +-
 doc/src/sgml/func.sgml                       |    7 +-
 src/backend/catalog/dependency.c             |   19 +-
 src/backend/catalog/objectaddress.c          | 1002 ++++++++++++------
 src/backend/catalog/pg_depend.c              |    4 +-
 src/backend/catalog/pg_shdepend.c            |    8 +-
 src/backend/commands/event_trigger.c         |    9 +-
 src/backend/commands/extension.c             |    6 +-
 src/backend/commands/tablecmds.c             |   10 +-
 src/backend/utils/adt/regproc.c              |   22 +-
 src/include/catalog/objectaddress.h          |   12 +-
 src/include/utils/regproc.h                  |    4 +-
 src/test/regress/expected/object_address.out |  708 +++++++++++++
 src/test/regress/sql/object_address.sql      |  124 +++
 19 files changed, 1615 insertions(+), 374 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..ffb84ac6b7 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index dba0986e02..0e13dba851 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -364,7 +364,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -535,7 +535,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 53a40ddeec..6154c8a814 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17703,7 +17703,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17719,7 +17720,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17734,6 +17736,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365357..52387aaac7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -605,11 +605,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -689,11 +689,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -785,12 +785,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -884,7 +884,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -907,7 +907,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -965,7 +966,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 644cb64c0c..948d3b24af 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -753,14 +753,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1629,7 +1635,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1660,7 +1666,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2669,10 +2675,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true. If missing_ok is false an
+ * error is generated instead.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2681,22 +2689,28 @@ getObjectDescription(const ObjectAddress *object)
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationDescription(&buffer, object->objectId);
+			getRelationDescription(&buffer, object->objectId, missing_ok);
 			if (object->objectSubId != 0)
-				appendStringInfo(&buffer, _(" column %s"),
-								 get_attname(object->objectId,
-											 object->objectSubId,
-											 false));
+			{
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (attname)
+					appendStringInfo(&buffer, _(" column %s"), attname);
+			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  FORMAT_TYPE_ALLOW_INVALID));
 			break;
 
 		case OCLASS_CAST:
@@ -2719,15 +2733,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					castForm = (Form_pg_cast) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("cast from %s to %s"),
+									 format_type_be(castForm->castsource),
+									 format_type_be(castForm->casttarget));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for cast %u",
 						 object->objectId);
-
-				castForm = (Form_pg_cast) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("cast from %s to %s"),
-								 format_type_be(castForm->castsource),
-								 format_type_be(castForm->casttarget));
+				}
 
 				systable_endscan(rcscan);
 				heap_close(castDesc, AccessShareLock);
@@ -2741,13 +2759,18 @@ getObjectDescription(const ObjectAddress *object)
 
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
-				if (!HeapTupleIsValid(collTup))
+				if (HeapTupleIsValid(collTup))
+				{
+					coll = (Form_pg_collation) GETSTRUCT(collTup);
+					appendStringInfo(&buffer, _("collation %s"),
+									 NameStr(coll->collname));
+					ReleaseSysCache(collTup);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "cache lookup failed for collation %u",
 						 object->objectId);
-				coll = (Form_pg_collation) GETSTRUCT(collTup);
-				appendStringInfo(&buffer, _("collation %s"),
-								 NameStr(coll->collname));
-				ReleaseSysCache(collTup);
+				}
 				break;
 			}
 
@@ -2759,8 +2782,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2768,7 +2796,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
 					pfree(rel.data);
@@ -2790,8 +2818,12 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("conversion %s"),
 								 NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
 				ReleaseSysCache(conTup);
@@ -2819,18 +2851,22 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
+
+					appendStringInfo(&buffer, _("default for %s"),
+									 getObjectDescription(&colobject, false));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for attrdef %u",
 						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
-
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
-
-				appendStringInfo(&buffer, _("default for %s"),
-								 getObjectDescription(&colobject));
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2838,9 +2874,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2848,6 +2890,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2863,8 +2906,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2891,7 +2939,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2901,8 +2949,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2931,28 +2984,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amopForm = (Form_pg_amop) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
+
+					/*------
+					   translator: %d is the operator strategy (a number), the
+					   first two %s's are data type names, the third %s is the
+					   description of the operator family, and the last %s is
+					   the textual form of the operator with arguments.  */
+					appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
+									 amopForm->amopstrategy,
+									 format_type_be(amopForm->amoplefttype),
+									 format_type_be(amopForm->amoprighttype),
+									 opfam.data,
+									 format_operator(amopForm->amopopr));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amop entry %u",
 						 object->objectId);
-
-				amopForm = (Form_pg_amop) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
-
-				/*------
-				   translator: %d is the operator strategy (a number), the
-				   first two %s's are data type names, the third %s is the
-				   description of the operator family, and the last %s is the
-				   textual form of the operator with arguments.  */
-				appendStringInfo(&buffer, _("operator %d (%s, %s) of %s: %s"),
-								 amopForm->amopstrategy,
-								 format_type_be(amopForm->amoplefttype),
-								 format_type_be(amopForm->amoprighttype),
-								 opfam.data,
-								 format_operator(amopForm->amopopr));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amopDesc, AccessShareLock);
@@ -2981,28 +3038,32 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(amscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
+
+					initStringInfo(&opfam);
+					getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
+
+					/*------
+					   translator: %d is the function number, the first two
+					   %s's are data type names, the third %s is the description
+					   of the operator family, and the last %s is the textual
+					   form of the function with arguments.  */
+					appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
+									 amprocForm->amprocnum,
+									 format_type_be(amprocForm->amproclefttype),
+									 format_type_be(amprocForm->amprocrighttype),
+									 opfam.data,
+									 format_procedure(amprocForm->amproc));
+
+					pfree(opfam.data);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for amproc entry %u",
 						 object->objectId);
-
-				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
-
-				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
-
-				/*------
-				   translator: %d is the function number, the first two %s's
-				   are data type names, the third %s is the description of the
-				   operator family, and the last %s is the textual form of the
-				   function with arguments.  */
-				appendStringInfo(&buffer, _("function %d (%s, %s) of %s: %s"),
-								 amprocForm->amprocnum,
-								 format_type_be(amprocForm->amproclefttype),
-								 format_type_be(amprocForm->amprocrighttype),
-								 opfam.data,
-								 format_procedure(amprocForm->amproc));
-
-				pfree(opfam.data);
+				}
 
 				systable_endscan(amscan);
 				heap_close(amprocDesc, AccessShareLock);
@@ -3029,15 +3090,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(rcscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					rule = (Form_pg_rewrite) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("rule %s on "),
+									 NameStr(rule->rulename));
+					getRelationDescription(&buffer, rule->ev_class, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for rule %u",
 						 object->objectId);
-
-				rule = (Form_pg_rewrite) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("rule %s on "),
-								 NameStr(rule->rulename));
-				getRelationDescription(&buffer, rule->ev_class);
+				}
 
 				systable_endscan(rcscan);
 				heap_close(ruleDesc, AccessShareLock);
@@ -3064,15 +3129,19 @@ getObjectDescription(const ObjectAddress *object)
 
 				tup = systable_getnext(tgscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					trig = (Form_pg_trigger) GETSTRUCT(tup);
+
+					appendStringInfo(&buffer, _("trigger %s on "),
+									 NameStr(trig->tgname));
+					getRelationDescription(&buffer, trig->tgrelid, false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for trigger %u",
 						 object->objectId);
-
-				trig = (Form_pg_trigger) GETSTRUCT(tup);
-
-				appendStringInfo(&buffer, _("trigger %s on "),
-								 NameStr(trig->tgname));
-				getRelationDescription(&buffer, trig->tgrelid);
+				}
 
 				systable_endscan(tgscan);
 				heap_close(trigDesc, AccessShareLock);
@@ -3085,8 +3154,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3099,8 +3172,12 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
@@ -3118,8 +3195,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search parser %s"),
 								 NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname));
 				ReleaseSysCache(tup);
@@ -3133,8 +3214,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search dictionary %s"),
 								 NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname));
 				ReleaseSysCache(tup);
@@ -3148,8 +3233,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search template %s"),
 								 NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname));
 				ReleaseSysCache(tup);
@@ -3163,8 +3252,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("text search configuration %s"),
 								 NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname));
 				ReleaseSysCache(tup);
@@ -3173,8 +3266,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3184,8 +3280,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3196,8 +3296,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3206,8 +3310,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3215,8 +3321,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3231,8 +3338,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3270,8 +3382,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3328,8 +3447,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3341,8 +3464,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3369,15 +3496,20 @@ getObjectDescription(const ObjectAddress *object)
 
 				tuple = systable_getnext(sscan);
 
-				if (!HeapTupleIsValid(tuple))
+				if (HeapTupleIsValid(tuple))
+				{
+					form_policy = (Form_pg_policy) GETSTRUCT(tuple);
+
+					appendStringInfo(&buffer, _("policy %s on "),
+									 NameStr(form_policy->polname));
+					getRelationDescription(&buffer, form_policy->polrelid,
+										   false);
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for policy %u",
 						 object->objectId);
-
-				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
-
-				appendStringInfo(&buffer, _("policy %s on "),
-								 NameStr(form_policy->polname));
-				getRelationDescription(&buffer, form_policy->polrelid);
+				}
 
 				systable_endscan(sscan);
 				heap_close(policy_rel, AccessShareLock);
@@ -3386,9 +3518,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3401,8 +3535,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -3415,8 +3553,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3428,8 +3568,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3447,6 +3591,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3462,14 +3610,14 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
  * subroutine for getObjectDescription: describe a relation
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3479,7 +3627,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3540,7 +3692,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3550,7 +3702,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3594,7 +3750,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3609,6 +3769,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3682,8 +3844,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3706,8 +3874,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3724,6 +3898,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3751,23 +3926,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3781,7 +3970,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3791,11 +3980,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3811,7 +4002,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -3948,6 +4140,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3955,7 +4151,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3963,7 +4160,12 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		Assert(buffer->len == 0);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4010,7 +4212,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4019,7 +4221,13 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4037,7 +4245,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4045,7 +4254,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4062,12 +4275,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4076,11 +4290,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4102,31 +4318,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_ALLOW_INVALID | FORMAT_TYPE_FORCE_QUALIFY);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4144,8 +4370,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4172,8 +4404,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4194,15 +4430,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4217,7 +4458,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4236,8 +4478,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4272,19 +4518,24 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tup = systable_getnext(adscan);
 
-				if (!HeapTupleIsValid(tup))
+				if (HeapTupleIsValid(tup))
+				{
+					attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
+
+					colobject.classId = RelationRelationId;
+					colobject.objectId = attrdef->adrelid;
+					colobject.objectSubId = attrdef->adnum;
+
+					appendStringInfo(&buffer, "for %s",
+									 getObjectIdentityParts(&colobject,
+															objname, objargs,
+															false));
+				}
+				else if (!missing_ok)
+				{
 					elog(ERROR, "could not find tuple for attrdef %u",
 						 object->objectId);
-
-				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
-
-				colobject.classId = RelationRelationId;
-				colobject.objectId = attrdef->adrelid;
-				colobject.objectSubId = attrdef->adnum;
-
-				appendStringInfo(&buffer, "for %s",
-								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+				}
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4299,8 +4550,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4317,10 +4572,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4334,8 +4590,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4361,7 +4621,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4370,8 +4631,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4403,13 +4668,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4457,13 +4730,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4497,14 +4778,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4523,14 +4810,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4544,8 +4837,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4562,8 +4859,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4585,8 +4886,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4608,8 +4913,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4631,8 +4940,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4654,8 +4967,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4672,7 +4989,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4686,8 +5005,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4701,8 +5024,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4714,10 +5041,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4725,11 +5056,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4744,8 +5079,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4792,8 +5131,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4855,8 +5202,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -4875,8 +5226,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -4895,14 +5250,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -4914,11 +5275,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -4931,8 +5295,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
@@ -4942,7 +5310,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (objname)
 				{
-					getRelationIdentity(&buffer, prform->prrelid, objname);
+					getRelationIdentity(&buffer, prform->prrelid, objname,
+										false);
 					*objargs = list_make1(pubname);
 				}
 
@@ -4954,11 +5323,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -4975,8 +5347,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5002,20 +5380,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	/* an empty string is equivalent to no object defined */
+	if (buffer.len == 0)
+	{
+		Assert((objname == NULL || *objname == NIL) &&
+			   (objargs == NULL || *objargs == NIL));
+		return NULL;
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5025,7 +5403,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5054,7 +5436,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5063,7 +5446,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..35449e9aed 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1421,10 +1421,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2082,8 +2083,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2e4538146d..a4c622ee11 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0e95037dcf..c0999100ab 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9470,7 +9470,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9518,7 +9518,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9537,7 +9537,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9555,7 +9555,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9616,7 +9616,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..623346aafc 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -751,7 +756,7 @@ to_regoperator(PG_FUNCTION_ARGS)
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
  */
-static char *
+char *
 format_operator_internal(Oid operator_oid, bool force_qualify)
 {
 	char	   *result;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index bfd9d54c11..d74715eff7 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -492,3 +492,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 13 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ table pg_class
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 55faa71edf..0772173775 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -213,3 +213,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.17.0

#27Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#26)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, May 16, 2018 at 01:03:18PM +0900, Michael Paquier wrote:

Okay, I have done so in the updated set attached. I have added some
documentation as well in fdwhandler.sgml about those two new things.
That's too late for v11 of course, so let's them sit until the time
comes.

Attached are refreshed versions for this commit fest. The deal for this
commit fest is to not consider large patches this time, so please let me
suggest to discard 0003 even if it is very mechanical. I would like
however to get 0001 and 0002 merged during this commit fest so as we can
move on with this thread as those are simple changes, and finish 0003
afterwards.
--
Michael

Attachments:

0001-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From ecd826a82b43011a831dc0b52d0b2a2d5d09fa9a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 1 Jul 2018 23:26:10 +0900
Subject: [PATCH 1/3] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object. This is added in a set of new routines to not impact
unnecessrily any FPW plugins.
---
 doc/src/sgml/fdwhandler.sgml  | 30 +++++++++++++++++++++++++++++
 src/backend/foreign/foreign.c | 36 +++++++++++++++++++++++++++++++++--
 src/include/foreign/foreign.h |  4 ++++
 3 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 7b758bdf09..bfc538f249 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1404,6 +1404,21 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname>
+     object for the foreign-data wrapper with the given OID.  A
+     <structname>ForeignDataWrapper</structname> object contains properties
+     of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     FDW.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid);
 </programlisting>
 
@@ -1416,6 +1431,21 @@ GetForeignDataWrapper(Oid fdwid);
     <para>
 <programlisting>
 ForeignServer *
+GetForeignServerExtended(Oid serverid, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object
+     for the foreign server with the given OID.  A
+     <structname>ForeignServer</structname> object contains properties
+     of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
 GetForeignServer(Oid serverid);
 </programlisting>
 
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index eac78a5d31..01b5175e71 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -33,6 +33,18 @@
  */
 ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid)
+{
+	return GetForeignDataWrapperExtended(fdwid, false);
+}
+
+
+/*
+ * GetForeignDataWrapperExtended -	look up the foreign-data wrapper
+ * by OID. If missing_ok is true, return NULL if the object cannot be
+ * found instead of raising an error.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +55,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -91,6 +107,18 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  */
 ForeignServer *
 GetForeignServer(Oid serverid)
+{
+	return GetForeignServerExtended(serverid, false);
+}
+
+
+/*
+ * GetForeignServerExtended - look up the foreign server definition. If
+ * missing_ok is true, return NULL if the object cannot be found instead
+ * of raising an error.
+ */
+ForeignServer *
+GetForeignServerExtended(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +129,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..5cc89e967c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -70,9 +70,13 @@ typedef struct ForeignTable
 
 
 extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServerExtended(Oid serverid,
+							bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
 extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapperExtended(Oid fdwid,
+							bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.18.0

0002-Refactor-routines-for-subscription-and-publication-l.patchtext/x-diff; charset=us-asciiDownload
From 958f2fbf2cd7c2caa054bfa68eb265139433cf66 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 1 Jul 2018 23:26:28 +0900
Subject: [PATCH 2/3] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  | 11 +++++++++--
 src/backend/catalog/pg_subscription.c | 11 +++++++++--
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7db942dcba..a0d13181a2 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3508,7 +3508,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3526,7 +3527,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
 				getRelationDescription(&rel, prform->prrelid);
@@ -3542,7 +3543,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -5042,7 +5043,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -5063,7 +5064,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				getRelationIdentity(&buffer, prform->prrelid, objname);
 				appendStringInfo(&buffer, " in publication %s", pubname);
@@ -5079,7 +5080,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ec3bd1d22d..3ecf6d57bf 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -427,9 +427,12 @@ get_publication_oid(const char *pubname, bool missing_ok)
 
 /*
  * get_publication_name - given a publication Oid, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -438,7 +441,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+		return NULL;
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8705d8b1d3..f891ff8054 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -179,9 +179,12 @@ get_subscription_oid(const char *subname, bool missing_ok)
 
 /*
  * get_subscription_name - given a subscription OID, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +193,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for subscription %u", subid);
+		return NULL;
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 83c59cb10d..a5d5570f76 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -88,7 +88,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 82dd6faf23..e4dc771cf5 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -80,7 +80,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.18.0

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From aa9368b0dd551b16eeb1a3fc5461f3b6ac80eb75 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 1 Jul 2018 23:26:43 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 910 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1593 insertions(+), 302 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..ffb84ac6b7 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index dba0986e02..0e13dba851 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -364,7 +364,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -535,7 +535,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index edc9be92a6..24a74f5075 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17719,7 +17719,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17735,7 +17736,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    identifier of the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the precise format
    depending on object type, and each part within the format being
-   schema-qualified and quoted as necessary.
+   schema-qualified and quoted as necessary. Undefined objects are identified
+   with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17750,6 +17752,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365357..52387aaac7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -605,11 +605,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -689,11 +689,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -785,12 +785,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -884,7 +884,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -907,7 +907,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -965,7 +966,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index a0d13181a2..8ffa98c5df 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -753,14 +753,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1629,7 +1635,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1660,7 +1666,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2669,10 +2675,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2682,14 +2690,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2701,13 +2714,16 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  FORMAT_TYPE_ALLOW_INVALID));
 			break;
 
 		case OCLASS_CAST:
@@ -2731,8 +2747,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2754,8 +2779,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2779,8 +2811,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2788,7 +2825,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2813,8 +2850,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2852,8 +2894,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2863,7 +2912,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2871,9 +2920,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2881,6 +2936,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2896,8 +2952,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2924,7 +2985,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2934,8 +2995,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2965,13 +3031,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3015,13 +3088,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3064,12 +3144,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3102,12 +3190,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3124,8 +3220,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3139,8 +3239,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3166,8 +3271,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3192,8 +3301,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3218,8 +3332,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3244,8 +3363,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3263,8 +3387,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3274,8 +3401,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3286,8 +3417,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3296,8 +3431,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3305,8 +3442,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3321,8 +3459,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3362,8 +3505,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3446,8 +3596,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3459,8 +3613,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3489,12 +3647,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3507,9 +3673,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3523,14 +3691,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3542,8 +3714,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3555,8 +3729,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3574,6 +3752,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3589,7 +3771,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3598,7 +3780,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3608,7 +3790,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3669,7 +3855,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3679,7 +3865,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3723,7 +3913,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3738,6 +3932,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3811,8 +4007,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3835,8 +4037,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3853,6 +4061,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3880,23 +4089,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3910,7 +4133,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3920,11 +4143,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3940,7 +4165,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4077,6 +4303,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4084,7 +4314,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4092,7 +4323,12 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		Assert(buffer->len == 0);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4139,7 +4375,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4148,7 +4384,13 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4166,7 +4408,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4174,7 +4417,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4191,12 +4438,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4205,11 +4453,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4231,31 +4481,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_ALLOW_INVALID | FORMAT_TYPE_FORCE_QUALIFY);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4273,8 +4533,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4301,8 +4567,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4323,15 +4593,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4346,7 +4621,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4365,8 +4641,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4402,8 +4682,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4413,7 +4700,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4428,8 +4716,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4446,10 +4738,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4463,8 +4756,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4490,7 +4787,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4499,8 +4797,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4532,13 +4834,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4586,13 +4896,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4626,14 +4944,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4652,14 +4976,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4673,8 +5003,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4691,8 +5025,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4714,8 +5052,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4737,8 +5079,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4760,8 +5106,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4783,8 +5133,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4801,7 +5155,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4815,8 +5171,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4830,8 +5190,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4843,10 +5207,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4854,11 +5222,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4873,8 +5245,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4921,8 +5297,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4984,8 +5368,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5004,8 +5392,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5024,14 +5416,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5043,11 +5441,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5060,13 +5461,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5080,11 +5485,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5101,8 +5509,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5128,20 +5542,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5151,7 +5579,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5180,7 +5612,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5189,7 +5622,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..35449e9aed 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1421,10 +1421,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2082,8 +2083,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2e4538146d..a4c622ee11 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7c0cf0d7ee..2d6caf09b3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9483,7 +9483,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9531,7 +9531,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9550,7 +9550,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9568,7 +9568,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9629,7 +9629,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..c42e78792c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 4085e451e4..059a983bae 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -499,3 +499,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index d7df322873..740424ba87 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -218,3 +218,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.18.0

#28Andrew Dunstan
andrew.dunstan@2ndquadrant.com
In reply to: Michael Paquier (#27)
Re: Cache lookup errors with functions manipulation object addresses

On 07/01/2018 10:27 AM, Michael Paquier wrote:

On Wed, May 16, 2018 at 01:03:18PM +0900, Michael Paquier wrote:

Okay, I have done so in the updated set attached. I have added some
documentation as well in fdwhandler.sgml about those two new things.
That's too late for v11 of course, so let's them sit until the time
comes.

Attached are refreshed versions for this commit fest. The deal for this
commit fest is to not consider large patches this time, so please let me
suggest to discard 0003 even if it is very mechanical. I would like
however to get 0001 and 0002 merged during this commit fest so as we can
move on with this thread as those are simple changes, and finish 0003
afterwards.

I think you're asserting far too broad a policy for the CF, and in any
case there has been no discussion of what exactly is a large patch. I
don't see any great need to defer patch 3. It is substantial although
not what I would class as large, but it also has relatively low impact,
ISTM.

cheers

andrew

--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#29Michael Paquier
michael@paquier.xyz
In reply to: Andrew Dunstan (#28)
Re: Cache lookup errors with functions manipulation object addresses

On Sun, Jul 01, 2018 at 12:31:17PM -0400, Andrew Dunstan wrote:

I think you're asserting far too broad a policy for the CF, and in any case
there has been no discussion of what exactly is a large patch. I don't see
any great need to defer patch 3. It is substantial although not what I would
class as large, but it also has relatively low impact, ISTM.

I am fine with any conclusion. As the patch has rotten a bit, I
switched it from "Ready for committer" to "Needs Review" by the way.
That seems more appropriate as the thing has rotten a bit.
--
Michael

#30Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#29)
Re: Cache lookup errors with functions manipulation object addresses

On Mon, Jul 02, 2018 at 08:54:25PM +0900, Michael Paquier wrote:

I am fine with any conclusion. As the patch has rotten a bit, I
switched it from "Ready for committer" to "Needs Review" by the way.
That seems more appropriate as the thing has rotten a bit.

Attached are rebased versions. This has been around for some time, so I
am planning to move on with this patch set pretty soon as that's mainly
cleanup for getObjectIdentity as it triggers elog("cache lookup") or
such for undefined objects. Patch 0001 extends FDW and server routines
so as it is possible to skip missing entries, without breaking
compatibility. Patch 0002 adds a missing_ok flag when doing
subscription and publication lookups.

Any objections?
--
Michael

#31Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#30)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Fri, Sep 14, 2018 at 11:22:12AM +0900, Michael Paquier wrote:

Attached are rebased versions. This has been around for some time, so I
am planning to move on with this patch set pretty soon as that's mainly
cleanup for getObjectIdentity as it triggers elog("cache lookup") or
such for undefined objects. Patch 0001 extends FDW and server routines
so as it is possible to skip missing entries, without breaking
compatibility. Patch 0002 adds a missing_ok flag when doing
subscription and publication lookups.

Any objections?

And I forgot to attach the patches..
--
Michael

Attachments:

0001-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From fba6464f1555ec05029a58e2bf378ef83ce73172 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 1 Jul 2018 23:26:10 +0900
Subject: [PATCH 1/3] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument able to control if an error or a
NULL object is returned to the caller in the event of an undefined
object. This is added in a set of new routines to not impact
unnecessrily any FPW plugins.
---
 doc/src/sgml/fdwhandler.sgml  | 30 +++++++++++++++++++++++++++++
 src/backend/foreign/foreign.c | 36 +++++++++++++++++++++++++++++++++--
 src/include/foreign/foreign.h |  4 ++++
 3 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 4ce88dd77c..a68e264261 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1408,6 +1408,21 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname>
+     object for the foreign-data wrapper with the given OID.  A
+     <structname>ForeignDataWrapper</structname> object contains properties
+     of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     FDW.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid);
 </programlisting>
 
@@ -1420,6 +1435,21 @@ GetForeignDataWrapper(Oid fdwid);
     <para>
 <programlisting>
 ForeignServer *
+GetForeignServerExtended(Oid serverid, bool missing_ok);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object
+     for the foreign server with the given OID.  A
+     <structname>ForeignServer</structname> object contains properties
+     of the server (see <filename>foreign/foreign.h</filename> for details).
+     If <literal>missing_ok</literal> is true, a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     foreign server.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
 GetForeignServer(Oid serverid);
 </programlisting>
 
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index eac78a5d31..01b5175e71 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -33,6 +33,18 @@
  */
 ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid)
+{
+	return GetForeignDataWrapperExtended(fdwid, false);
+}
+
+
+/*
+ * GetForeignDataWrapperExtended -	look up the foreign-data wrapper
+ * by OID. If missing_ok is true, return NULL if the object cannot be
+ * found instead of raising an error.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bool missing_ok)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +55,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -91,6 +107,18 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  */
 ForeignServer *
 GetForeignServer(Oid serverid)
+{
+	return GetForeignServerExtended(serverid, false);
+}
+
+
+/*
+ * GetForeignServerExtended - look up the foreign server definition. If
+ * missing_ok is true, return NULL if the object cannot be found instead
+ * of raising an error.
+ */
+ForeignServer *
+GetForeignServerExtended(Oid serverid, bool missing_ok)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +129,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..5cc89e967c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -70,9 +70,13 @@ typedef struct ForeignTable
 
 
 extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServerExtended(Oid serverid,
+							bool missing_ok);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
 extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapperExtended(Oid fdwid,
+							bool missing_ok);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.19.0

0002-Refactor-routines-for-subscription-and-publication-l.patchtext/x-diff; charset=us-asciiDownload
From 22abc720b0bb514acbc9eff3881a06a7f247b6e2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 1 Jul 2018 23:26:28 +0900
Subject: [PATCH 2/3] Refactor routines for subscription and publication
 lookups

Those routines gain a missing_ok argument, allowing a caller to get a
NULL result instead of an error if set to true.
---
 src/backend/catalog/objectaddress.c   | 13 +++++++------
 src/backend/catalog/pg_publication.c  | 11 +++++++++--
 src/backend/catalog/pg_subscription.c | 11 +++++++++--
 src/include/catalog/pg_publication.h  |  2 +-
 src/include/catalog/pg_subscription.h |  2 +-
 5 files changed, 27 insertions(+), 12 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7db942dcba..a0d13181a2 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3508,7 +3508,8 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_PUBLICATION:
 			{
 				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId));
+								 get_publication_name(object->objectId,
+													  false));
 				break;
 			}
 
@@ -3526,7 +3527,7 @@ getObjectDescription(const ObjectAddress *object)
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
 				getRelationDescription(&rel, prform->prrelid);
@@ -3542,7 +3543,7 @@ getObjectDescription(const ObjectAddress *object)
 		case OCLASS_SUBSCRIPTION:
 			{
 				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId));
+								 get_subscription_name(object->objectId, false));
 				break;
 			}
 
@@ -5042,7 +5043,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId);
+				pubname = get_publication_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(pubname));
 				if (objname)
@@ -5063,7 +5064,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 object->objectId);
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
-				pubname = get_publication_name(prform->prpubid);
+				pubname = get_publication_name(prform->prpubid, false);
 
 				getRelationIdentity(&buffer, prform->prrelid, objname);
 				appendStringInfo(&buffer, " in publication %s", pubname);
@@ -5079,7 +5080,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId);
+				subname = get_subscription_name(object->objectId, false);
 				appendStringInfoString(&buffer,
 									   quote_identifier(subname));
 				if (objname)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index ec3bd1d22d..3ecf6d57bf 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -427,9 +427,12 @@ get_publication_oid(const char *pubname, bool missing_ok)
 
 /*
  * get_publication_name - given a publication Oid, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_publication_name(Oid pubid)
+get_publication_name(Oid pubid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *pubname;
@@ -438,7 +441,11 @@ get_publication_name(Oid pubid)
 	tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for publication %u", pubid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for publication %u", pubid);
+		return NULL;
+	}
 
 	pubform = (Form_pg_publication) GETSTRUCT(tup);
 	pubname = pstrdup(NameStr(pubform->pubname));
diff --git a/src/backend/catalog/pg_subscription.c b/src/backend/catalog/pg_subscription.c
index 8705d8b1d3..f891ff8054 100644
--- a/src/backend/catalog/pg_subscription.c
+++ b/src/backend/catalog/pg_subscription.c
@@ -179,9 +179,12 @@ get_subscription_oid(const char *subname, bool missing_ok)
 
 /*
  * get_subscription_name - given a subscription OID, look up the name
+ *
+ * If missing_ok is false, throw an error if name not found.  If true, just
+ * return NULL.
  */
 char *
-get_subscription_name(Oid subid)
+get_subscription_name(Oid subid, bool missing_ok)
 {
 	HeapTuple	tup;
 	char	   *subname;
@@ -190,7 +193,11 @@ get_subscription_name(Oid subid)
 	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
 
 	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "cache lookup failed for subscription %u", subid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for subscription %u", subid);
+		return NULL;
+	}
 
 	subform = (Form_pg_subscription) GETSTRUCT(tup);
 	subname = pstrdup(NameStr(subform->subname));
diff --git a/src/include/catalog/pg_publication.h b/src/include/catalog/pg_publication.h
index 83c59cb10d..a5d5570f76 100644
--- a/src/include/catalog/pg_publication.h
+++ b/src/include/catalog/pg_publication.h
@@ -88,7 +88,7 @@ extern ObjectAddress publication_add_relation(Oid pubid, Relation targetrel,
 						 bool if_not_exists);
 
 extern Oid	get_publication_oid(const char *pubname, bool missing_ok);
-extern char *get_publication_name(Oid pubid);
+extern char *get_publication_name(Oid pubid, bool missing_ok);
 
 extern Datum pg_get_publication_tables(PG_FUNCTION_ARGS);
 
diff --git a/src/include/catalog/pg_subscription.h b/src/include/catalog/pg_subscription.h
index 82dd6faf23..e4dc771cf5 100644
--- a/src/include/catalog/pg_subscription.h
+++ b/src/include/catalog/pg_subscription.h
@@ -80,7 +80,7 @@ typedef struct Subscription
 extern Subscription *GetSubscription(Oid subid, bool missing_ok);
 extern void FreeSubscription(Subscription *sub);
 extern Oid	get_subscription_oid(const char *subname, bool missing_ok);
-extern char *get_subscription_name(Oid subid);
+extern char *get_subscription_name(Oid subid, bool missing_ok);
 
 extern int	CountDBSubscriptions(Oid dbid);
 
-- 
2.19.0

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From 417c623e6e418ef7e038ad0096e73e8a5cd3da04 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Sun, 1 Jul 2018 23:26:43 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 910 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1593 insertions(+), 302 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..ffb84ac6b7 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index dba0986e02..0e13dba851 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -364,7 +364,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -535,7 +535,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4331bebc96..0eebadb6fa 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17785,7 +17785,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17801,7 +17802,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17817,6 +17819,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365357..52387aaac7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -605,11 +605,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -689,11 +689,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -785,12 +785,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -884,7 +884,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -907,7 +907,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -965,7 +966,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index a0d13181a2..8ffa98c5df 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -753,14 +753,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1629,7 +1635,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1660,7 +1666,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2669,10 +2675,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2682,14 +2690,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2701,13 +2714,16 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  FORMAT_TYPE_ALLOW_INVALID));
 			break;
 
 		case OCLASS_CAST:
@@ -2731,8 +2747,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2754,8 +2779,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2779,8 +2811,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2788,7 +2825,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2813,8 +2850,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2852,8 +2894,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2863,7 +2912,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2871,9 +2920,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2881,6 +2936,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2896,8 +2952,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2924,7 +2985,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2934,8 +2995,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2965,13 +3031,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3015,13 +3088,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3064,12 +3144,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3102,12 +3190,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3124,8 +3220,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3139,8 +3239,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3166,8 +3271,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3192,8 +3301,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3218,8 +3332,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3244,8 +3363,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3263,8 +3387,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3274,8 +3401,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3286,8 +3417,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3296,8 +3431,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3305,8 +3442,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3321,8 +3459,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3362,8 +3505,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3446,8 +3596,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3459,8 +3613,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3489,12 +3647,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3507,9 +3673,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3523,14 +3691,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3542,8 +3714,10 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId, false));
+				char *subname = get_subscription_name(object->objectId, missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3555,8 +3729,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3574,6 +3752,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3589,7 +3771,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3598,7 +3780,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3608,7 +3790,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3669,7 +3855,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3679,7 +3865,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3723,7 +3913,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3738,6 +3932,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3811,8 +4007,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3835,8 +4037,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3853,6 +4061,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3880,23 +4089,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3910,7 +4133,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3920,11 +4143,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3940,7 +4165,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4077,6 +4303,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4084,7 +4314,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4092,7 +4323,12 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		Assert(buffer->len == 0);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4139,7 +4375,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4148,7 +4384,13 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4166,7 +4408,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4174,7 +4417,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4191,12 +4438,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4205,11 +4453,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4231,31 +4481,41 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
+			getRelationIdentity(&buffer, object->objectId, objname,
+								missing_ok);
+			if (objname && *objname == NIL)
+				break;
 			if (object->objectSubId != 0)
 			{
-				char	   *attr;
-
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				char	   *attr = get_attname(object->objectId,
+											   object->objectSubId,
+											   missing_ok);
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s", quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_ALLOW_INVALID | FORMAT_TYPE_FORCE_QUALIFY);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4273,8 +4533,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4301,8 +4567,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4323,15 +4593,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4346,7 +4621,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4365,8 +4641,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4402,8 +4682,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4413,7 +4700,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4428,8 +4716,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4446,10 +4738,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4463,8 +4756,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4490,7 +4787,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4499,8 +4797,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4532,13 +4834,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4586,13 +4896,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4626,14 +4944,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4652,14 +4976,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4673,8 +5003,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4691,8 +5025,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4714,8 +5052,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4737,8 +5079,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4760,8 +5106,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4783,8 +5133,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4801,7 +5155,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4815,8 +5171,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4830,8 +5190,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4843,10 +5207,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4854,11 +5222,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4873,8 +5245,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4921,8 +5297,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4984,8 +5368,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5004,8 +5392,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5024,14 +5416,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5043,11 +5441,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5060,13 +5461,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5080,11 +5485,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5101,8 +5509,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5128,20 +5542,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5151,7 +5579,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5180,7 +5612,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5189,7 +5622,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..35449e9aed 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1421,10 +1421,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2082,8 +2083,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2d761a5773..69d1e48e57 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2824,7 +2824,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3214,7 +3214,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3254,7 +3254,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e96512e051..4c4aa163bf 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9515,7 +9515,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9563,7 +9563,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9582,7 +9582,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9600,7 +9600,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9661,7 +9661,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..c42e78792c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 4085e451e4..059a983bae 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -499,3 +499,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |     object_names      | object_args 
+--------------+-----------------------+-------------
+ table column | {pg_catalog,pg_class} | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index d7df322873..740424ba87 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -218,3 +218,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.19.0

#32Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#31)
Re: Cache lookup errors with functions manipulation object addresses

On 2018-Sep-14, Michael Paquier wrote:

On Fri, Sep 14, 2018 at 11:22:12AM +0900, Michael Paquier wrote:

Attached are rebased versions. This has been around for some time, so I
am planning to move on with this patch set pretty soon as that's mainly
cleanup for getObjectIdentity as it triggers elog("cache lookup") or
such for undefined objects. Patch 0001 extends FDW and server routines
so as it is possible to skip missing entries, without breaking
compatibility. Patch 0002 adds a missing_ok flag when doing
subscription and publication lookups.

Any objections?

And I forgot to attach the patches..

Patches 0001 and 0002 look OK to me now, on a cursory glance.

I haven't looked at 0003 yet.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#33Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#32)
Re: Cache lookup errors with functions manipulation object addresses

On 2018-Sep-14, Alvaro Herrera wrote:

I haven't looked at 0003 yet.

It's strange that pg_identify_object returns empty type in only some
cases (as seen in the regression test output) ... and this one
definitely does not make sense:

+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#34Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#33)
2 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Fri, Sep 14, 2018 at 12:07:23PM -0300, Alvaro Herrera wrote:

On 2018-Sep-14, Alvaro Herrera wrote:

I haven't looked at 0003 yet.

Thanks for the review. I have pushed 0002 for now. I had one doubt
about 0001 though. So as to avoid redesigning the APIs for FDWs and
servers again in the future, wouldn't we want to give them a uint32
flags which can be in with more than one option. There would be only
one option for now, which is what I have done in the attached.

It's strange that pg_identify_object returns empty type in only some
cases (as seen in the regression test output) ...

Are you referring to something in particular? All the routines used in
objectaddress.c are part of what exists in core for some time now,
particularly handling for operators, functions and types has been sort
of messy. It is of course possible to reach a state in the code where
we have options to get NULL results for all those things. For example,
format_procedure_internal could be refactored in a way similar to what
happens for format_type_extended. And format_type_extended could gain a
FORCE_NULL flag which makes sure that on an undefined object
objectaddress.c sees a NULL object. That was one of the points raised
upthread that I still wanted to discuss.. One thing is that I am not
sure if it is worth complicating the existing interface even more just
to deal with undefined objects as pure NULLs. Thoughts are welcome.

And this one definitely does not make sense:

+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+     type     |   schema   |   name   |      identity       
+--------------+------------+----------+---------------------
+ table column | pg_catalog | pg_class | pg_catalog.pg_class
+(1 row)

Indeed, this one is not intentional, and can be easily addressed by
checking first for the attribute existence. This is corrected in the
attached version.
--
Michael

Attachments:

0001-Extend-lookup-routines-for-FDW-and-foreign-server-wi.patchtext/x-diff; charset=us-asciiDownload
From f4dd9741833e4499c1d7698dc869e2f8b9bfb5ce Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Sep 2018 14:17:09 +0900
Subject: [PATCH 1/2] Extend lookup routines for FDW and foreign server with
 NULL handling

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument to pass flags.  The only value which
can be used now is to indicate if it a missing object should result in
an error or not.  Those new routines are added into the existing set and
documented in consequence.
---
 doc/src/sgml/fdwhandler.sgml  | 34 +++++++++++++++++++++++++++++++++
 src/backend/foreign/foreign.c | 36 +++++++++++++++++++++++++++++++++--
 src/include/foreign/foreign.h | 16 ++++++++++++++++
 3 files changed, 84 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 4ce88dd77c..6fe6122568 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1408,6 +1408,23 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, uint32 flags);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname>
+     object for the foreign-data wrapper with the given OID.  A
+     <structname>ForeignDataWrapper</structname> object contains properties
+     of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     <structfield>flags</structfield> is a bitwise-or'd bit mask indicating
+     an extra set of options.  It can take the value
+     <literal>FDW_MISSING_OK</literal>, in which case a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     object.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid);
 </programlisting>
 
@@ -1420,6 +1437,23 @@ GetForeignDataWrapper(Oid fdwid);
     <para>
 <programlisting>
 ForeignServer *
+GetForeignServerExtended(Oid serverid, uint32 flags);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object
+     for the foreign server with the given OID.  A
+     <structname>ForeignServer</structname> object contains properties
+     of the server (see <filename>foreign/foreign.h</filename> for details).
+     <structfield>flags</structfield> is a bitwise-or'd bit mask indicating
+     an extra set of options.  It can take the value
+     <literal>FSV_MISSING_OK</literal>, in which case a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     object.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
 GetForeignServer(Oid serverid);
 </programlisting>
 
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index eac78a5d31..88726d9d70 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -33,6 +33,18 @@
  */
 ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid)
+{
+	return GetForeignDataWrapperExtended(fdwid, 0);
+}
+
+
+/*
+ * GetForeignDataWrapperExtended -	look up the foreign-data wrapper
+ * by OID. If flags uses FDW_MISSING_OK, return NULL if the object cannot
+ * be found instead of raising an error.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, uint32 flags)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +55,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if ((flags & FDW_MISSING_OK) == 0)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -91,6 +107,18 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  */
 ForeignServer *
 GetForeignServer(Oid serverid)
+{
+	return GetForeignServerExtended(serverid, 0);
+}
+
+
+/*
+ * GetForeignServerExtended - look up the foreign server definition. If
+ * flags uses FSV_MISSING_OK, return NULL if the object cannot be found
+ * instead of raising an error.
+ */
+ForeignServer *
+GetForeignServerExtended(Oid serverid, uint32 flags)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +129,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if ((flags & FSV_MISSING_OK) == 0)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..c69af057fa 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -68,11 +68,27 @@ typedef struct ForeignTable
 	List	   *options;		/* ftoptions as DefElem list */
 } ForeignTable;
 
+/* Flags for GetForeignServerExtended */
+typedef enum ForeignServerExtendedOptions
+{
+	FSV_MISSING_OK = 1 << 0		/* no error if missing */
+} ForeignServerExtendedOptions;
+
+/* Flags for GetForeignDataWrapperExtended */
+typedef enum ForeignDataWrapperExtendedOptions
+{
+	FDW_MISSING_OK = 1 << 0		/* no error if missing */
+} ForeignDataWrapperExtendedOptions;
+
 
 extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServerExtended(Oid serverid,
+							uint32 flags);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
 extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapperExtended(Oid fdwid,
+							uint32 flags);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.19.0

0002-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From c83febdd53599c0d32f426cb987e4d338340eb12 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 18 Sep 2018 14:50:22 +0900
Subject: [PATCH 2/2] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 926 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 ++++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1608 insertions(+), 303 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index c641ec3565..dda2ff1cc4 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..ffb84ac6b7 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index dba0986e02..0e13dba851 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -364,7 +364,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -535,7 +535,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index c6a817d7c5..934a2cfaed 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index f0c22715aa..3f3b0bf17c 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index bc15a36a45..eca5350260 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4331bebc96..0eebadb6fa 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17785,7 +17785,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17801,7 +17802,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17817,6 +17819,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365357..52387aaac7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -605,11 +605,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -689,11 +689,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -785,12 +785,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -884,7 +884,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -907,7 +907,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -965,7 +966,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 593e6f7022..a3264594d4 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -753,14 +753,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1629,7 +1635,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1660,7 +1666,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2669,10 +2675,12 @@ get_catalog_object_by_oid(Relation catalog, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2682,14 +2690,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2701,13 +2714,16 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfo(&buffer, _("function %s"),
 							 format_procedure(object->objectId));
 			break;
 
 		case OCLASS_TYPE:
+			/* returns "???" for undefined type */
 			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
+							 format_type_extended(object->objectId, -1,
+												  FORMAT_TYPE_ALLOW_INVALID));
 			break;
 
 		case OCLASS_CAST:
@@ -2731,8 +2747,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2754,8 +2779,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2779,8 +2811,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2788,7 +2825,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2813,8 +2850,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2852,8 +2894,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2863,7 +2912,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2871,9 +2920,15 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
 			appendStringInfo(&buffer, _("large object %u"),
@@ -2881,6 +2936,7 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfo(&buffer, _("operator %s"),
 							 format_operator(object->objectId));
 			break;
@@ -2896,8 +2952,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2924,7 +2985,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2934,8 +2995,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -2965,13 +3031,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3015,13 +3088,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3064,12 +3144,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3102,12 +3190,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3124,8 +3220,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3139,8 +3239,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3166,8 +3271,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3192,8 +3301,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3218,8 +3332,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3244,8 +3363,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3263,8 +3387,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3274,8 +3401,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3286,8 +3417,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3296,8 +3431,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3305,8 +3442,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3321,8 +3459,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3362,8 +3505,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3446,8 +3596,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3459,8 +3613,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3489,12 +3647,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3507,9 +3673,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3523,14 +3691,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3542,9 +3714,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3556,8 +3730,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3575,6 +3753,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3590,7 +3772,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3599,7 +3781,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3609,7 +3791,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3670,7 +3856,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3680,7 +3866,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3724,7 +3914,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3739,6 +3933,8 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objtype;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3812,8 +4008,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	}
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	objtype = getObjectTypeDescription(&address, true);
+	if (objtype)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* schema name */
 	if (OidIsValid(schema_oid))
@@ -3836,8 +4038,14 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	objidentity = getObjectIdentity(&address, true);
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(getObjectIdentity(&address, true));
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3854,6 +4062,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	Oid			objid = PG_GETARG_OID(1);
 	int32		objsubid = PG_GETARG_INT32(2);
 	ObjectAddress address;
+	char	   *type;
 	char	   *identity;
 	List	   *names;
 	List	   *args;
@@ -3881,23 +4090,37 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
-	nulls[0] = false;
+	type = getObjectTypeDescription(&address, true);
+	if (type)
+	{
+		values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, false));
+		nulls[0] = false;
+	}
+	else
+		nulls[0] = true;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3911,7 +4134,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3921,11 +4144,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3941,7 +4166,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4078,6 +4304,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4085,7 +4315,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4093,7 +4324,12 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		Assert(buffer->len == 0);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4140,7 +4376,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4149,7 +4385,13 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrRel = heap_open(ConstraintRelationId, AccessShareLock);
 	constrTup = get_catalog_object_by_oid(constrRel, constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4167,7 +4409,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4175,7 +4418,11 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4192,12 +4439,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4206,11 +4454,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4232,31 +4482,55 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
+			/* returns numerical OID for undefined function */
 			appendStringInfoString(&buffer,
 								   format_procedure_qualified(object->objectId));
 			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
+				format_procedure_parts(object->objectId, objname, objargs,
+									   missing_ok);
 			break;
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				/* returns "???" for undefined type */
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_ALLOW_INVALID | FORMAT_TYPE_FORCE_QUALIFY);
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4274,8 +4548,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(castRel, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4302,8 +4582,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4324,15 +4608,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4347,7 +4636,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4366,8 +4656,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4403,8 +4697,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4414,7 +4715,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4429,8 +4731,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4447,10 +4753,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
+			/* returns numerical OID for undefined operator */
 			appendStringInfoString(&buffer,
 								   format_operator_qualified(object->objectId));
 			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
+				format_operator_parts(object->objectId, objname, objargs, missing_ok);
 			break;
 
 		case OCLASS_OPCLASS:
@@ -4464,8 +4771,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4491,7 +4802,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4500,8 +4812,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4533,13 +4849,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4587,13 +4911,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4627,14 +4959,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(ruleDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4653,14 +4991,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(trigDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4674,8 +5018,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4692,8 +5040,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4715,8 +5067,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4738,8 +5094,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4761,8 +5121,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4784,8 +5148,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4802,7 +5170,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4816,8 +5186,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4831,8 +5205,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4844,10 +5222,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4855,11 +5237,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4874,8 +5260,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4922,8 +5312,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -4985,8 +5383,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5005,8 +5407,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5025,14 +5431,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(polDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5044,11 +5456,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5061,13 +5476,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5081,11 +5500,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5102,8 +5524,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = get_catalog_object_by_oid(transformDesc, object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5129,20 +5557,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5152,7 +5594,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5181,7 +5627,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5190,7 +5637,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..35449e9aed 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1421,10 +1421,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2082,8 +2083,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 2d761a5773..69d1e48e57 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2824,7 +2824,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3214,7 +3214,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3254,7 +3254,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 03c0b739b3..362ff48bf0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9515,7 +9515,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9542,7 +9542,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9561,7 +9561,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9579,7 +9579,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9640,7 +9640,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..c42e78792c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -405,7 +405,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -415,7 +416,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -829,15 +834,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6a9b1eec73..5a1e2a8f23 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -68,14 +68,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..c09debbe26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -19,10 +19,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 4085e451e4..6a17fc9ec3 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -499,3 +499,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     |   schema   |   name   | identity 
+--------------+------------+----------+----------
+ table column | pg_catalog | pg_class | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | -
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | schema | name | identity 
+------+--------+------+----------
+      |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 0
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ function 0
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ type -
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ large object 0
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ operator 0
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ table column |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator | {}           | {}
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index d7df322873..51fdf12056 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -218,3 +218,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.19.0

#35Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#34)
Re: Cache lookup errors with functions manipulation object addresses

On 2018-Sep-18, Michael Paquier wrote:

On Fri, Sep 14, 2018 at 12:07:23PM -0300, Alvaro Herrera wrote:

On 2018-Sep-14, Alvaro Herrera wrote:

I haven't looked at 0003 yet.

Thanks for the review. I have pushed 0002 for now. I had one doubt
about 0001 though. So as to avoid redesigning the APIs for FDWs and
servers again in the future, wouldn't we want to give them a uint32
flags which can be in with more than one option. There would be only
one option for now, which is what I have done in the attached.

Agreed.

I think defining bit flags in an enum is weird; I'd use defines instead.
And (a purely stylistic point) I'd use bits32 rather than uint32. (I'm
probably alone in this opinion, since almost every interface passing
flags uses unsigned int of some size. But "bits" types are defined
precisely for this purpose!) Compare a61f5ab98638.

I support 0001 other than those two points.

It's strange that pg_identify_object returns empty type in only some
cases (as seen in the regression test output) ...

Are you referring to something in particular?

Yeah, these two cases:

+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)

All the other cases you add have a non-empty value in "type".

All the routines used in objectaddress.c are part of what exists in
core for some time now, particularly handling for operators, functions
and types has been sort of messy.

I think this is our chance to fix the mess. Previously (before I added
the SQL-access of the object addressing mechanism in 9.5 I mean) it
wasn't possible to get at this code at all with arbitrary input, so this
is all a "new" problem (I mean new in the last 5 years).

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#36Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#35)
2 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, Dec 12, 2018 at 02:21:29PM -0300, Alvaro Herrera wrote:

I think defining bit flags in an enum is weird; I'd use defines
instead.

Okay, I am fine with that.

And (a purely stylistic point) I'd use bits32 rather than uint32. (I'm
probably alone in this opinion, since almost every interface passing
flags uses unsigned int of some size. But "bits" types are defined
precisely for this purpose!) Compare a61f5ab98638.

Fine with that as well, let's do as you suggest then.

I support 0001 other than those two points.

Attached is an updated version for that as 0001. Thanks for the
review. Does that part look good to you now?

Yeah, these two cases:

+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+ type | object_names | object_args 
+------+--------------+-------------
+      |              | 
+(1 row)

"type" should show "relation" here, yes.

+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)

All the other cases you add have a non-empty value in "type".

On this one I would expect NULL for object_names and object_args, as
empty does not make sense for an undefined object, still "type" should
print... "type".

+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+ type | object_names | object_args
+------+--------------+-------------
+      |              |
+(1 row)
Constraints also are empty.
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args
+--------------+--------------+-------------
+ large object | {0}          | {}
+(1 row)
Large objects should return NULL for the last two fields.
There are a couple of other inconsistent cases with the existing sub-APIs:
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args
+------+--------------+-------------
+ type | {-}          | {}
+(1 row)
Here I would expect NULL for object_names and object_args.

I think this is our chance to fix the mess. Previously (before I added
the SQL-access of the object addressing mechanism in 9.5 I mean) it
wasn't possible to get at this code at all with arbitrary input, so this
is all a "new" problem (I mean new in the last 5 years).

This requires a bit more work with the low-level APIs grabbing the
printed information though. And I think that the following guidelines
make sense to work on as a base definition for undefined objects:
- pg_identify_object returns the object type name, NULL for the other fields.
- pg_describe_object returns just NULL.
- pg_identify_object_as_address returns the object type name and NULL
for the other fields.

There is some more refactoring work still needed for constraints, large
objects and functions, in a way similar to a26116c6. I am pretty happy
with the shape of 0001, so this could be applied, 0002 still needs to be
reworked so as all undefined object types behave as described above in a
consistent manner. Do those definitions make sense?
--
Michael

Attachments:

0001-Introduce-new-extended-routines-for-FDW-and-foreign-.patchtext/x-diff; charset=iso-8859-1Download
From 7d832462d64f8d8df417682cff234876ac154b41 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 13 Dec 2018 14:29:19 +0900
Subject: [PATCH 1/2] Introduce new extended routines for FDW and foreign
 server lookups
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The cache lookup routines for foreign-data wrappers and foreign servers
are extended with an extra argument to handle a set of flags.  The only
value which can be used now is to indicate if a missing object should
result in an error or not, and are designed to be extensible on need.
Those new routines are added into the existing set of user-visible
things and documented in consequence.

Author: Michael Paquier
Reviewed-by: Álvaro Herrera
Discussion: https://postgr.es/m/CAB7nPqSZxrSmdHK-rny7z8mi=EAFXJ5J-0RbzDw6aus=wB5azQ@mail.gmail.com
---
 doc/src/sgml/fdwhandler.sgml  | 34 +++++++++++++++++++++++++++++++++
 src/backend/foreign/foreign.c | 36 +++++++++++++++++++++++++++++++++--
 src/include/foreign/foreign.h | 10 ++++++++++
 3 files changed, 78 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 4ce88dd77c..452b776b9e 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -1408,6 +1408,23 @@ ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
     <para>
 <programlisting>
 ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bits16 flags);
+</programlisting>
+
+     This function returns a <structname>ForeignDataWrapper</structname>
+     object for the foreign-data wrapper with the given OID.  A
+     <structname>ForeignDataWrapper</structname> object contains properties
+     of the FDW (see <filename>foreign/foreign.h</filename> for details).
+     <structfield>flags</structfield> is a bitwise-or'd bit mask indicating
+     an extra set of options.  It can take the value
+     <literal>FDW_MISSING_OK</literal>, in which case a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     object.
+    </para>
+
+    <para>
+<programlisting>
+ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid);
 </programlisting>
 
@@ -1420,6 +1437,23 @@ GetForeignDataWrapper(Oid fdwid);
     <para>
 <programlisting>
 ForeignServer *
+GetForeignServerExtended(Oid serverid, bits16 flags);
+</programlisting>
+
+     This function returns a <structname>ForeignServer</structname> object
+     for the foreign server with the given OID.  A
+     <structname>ForeignServer</structname> object contains properties
+     of the server (see <filename>foreign/foreign.h</filename> for details).
+     <structfield>flags</structfield> is a bitwise-or'd bit mask indicating
+     an extra set of options.  It can take the value
+     <literal>FSV_MISSING_OK</literal>, in which case a <literal>NULL</literal>
+     result is returned to the caller instead of an error for an undefined
+     object.
+    </para>
+
+    <para>
+<programlisting>
+ForeignServer *
 GetForeignServer(Oid serverid);
 </programlisting>
 
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 989a58ad78..79661526a3 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -33,6 +33,18 @@
  */
 ForeignDataWrapper *
 GetForeignDataWrapper(Oid fdwid)
+{
+	return GetForeignDataWrapperExtended(fdwid, 0);
+}
+
+
+/*
+ * GetForeignDataWrapperExtended -	look up the foreign-data wrapper
+ * by OID. If flags uses FDW_MISSING_OK, return NULL if the object cannot
+ * be found instead of raising an error.
+ */
+ForeignDataWrapper *
+GetForeignDataWrapperExtended(Oid fdwid, bits16 flags)
 {
 	Form_pg_foreign_data_wrapper fdwform;
 	ForeignDataWrapper *fdw;
@@ -43,7 +55,11 @@ GetForeignDataWrapper(Oid fdwid)
 	tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+	{
+		if ((flags & FDW_MISSING_OK) == 0)
+			elog(ERROR, "cache lookup failed for foreign-data wrapper %u", fdwid);
+		return NULL;
+	}
 
 	fdwform = (Form_pg_foreign_data_wrapper) GETSTRUCT(tp);
 
@@ -91,6 +107,18 @@ GetForeignDataWrapperByName(const char *fdwname, bool missing_ok)
  */
 ForeignServer *
 GetForeignServer(Oid serverid)
+{
+	return GetForeignServerExtended(serverid, 0);
+}
+
+
+/*
+ * GetForeignServerExtended - look up the foreign server definition. If
+ * flags uses FSV_MISSING_OK, return NULL if the object cannot be found
+ * instead of raising an error.
+ */
+ForeignServer *
+GetForeignServerExtended(Oid serverid, bits16 flags)
 {
 	Form_pg_foreign_server serverform;
 	ForeignServer *server;
@@ -101,7 +129,11 @@ GetForeignServer(Oid serverid)
 	tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid));
 
 	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+	{
+		if ((flags & FSV_MISSING_OK) == 0)
+			elog(ERROR, "cache lookup failed for foreign server %u", serverid);
+		return NULL;
+	}
 
 	serverform = (Form_pg_foreign_server) GETSTRUCT(tp);
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 3ca12e64d2..38c9784c8c 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -68,11 +68,21 @@ typedef struct ForeignTable
 	List	   *options;		/* ftoptions as DefElem list */
 } ForeignTable;
 
+/* Flags for GetForeignServerExtended */
+#define FSV_MISSING_OK	0x01
+
+/* Flags for GetForeignDataWrapperExtended */
+#define FDW_MISSING_OK	0x01
+
 
 extern ForeignServer *GetForeignServer(Oid serverid);
+extern ForeignServer *GetForeignServerExtended(Oid serverid,
+							bits16 flags);
 extern ForeignServer *GetForeignServerByName(const char *name, bool missing_ok);
 extern UserMapping *GetUserMapping(Oid userid, Oid serverid);
 extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid);
+extern ForeignDataWrapper *GetForeignDataWrapperExtended(Oid fdwid,
+							bits16 flags);
 extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name,
 							bool missing_ok);
 extern ForeignTable *GetForeignTable(Oid relid);
-- 
2.20.0.rc2

0002-Eliminate-user-visible-cache-lookup-errors-for-objad.patch.gzapplication/gzipDownload
#37Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#36)
Re: Cache lookup errors with functions manipulation object addresses

On 2018-Dec-13, Michael Paquier wrote:

I support 0001 other than those two points.

Attached is an updated version for that as 0001. Thanks for the
review. Does that part look good to you now?

+1.

+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+ type | object_names | object_args 
+------+--------------+-------------
+      | {}           | {}
+(1 row)

All the other cases you add have a non-empty value in "type".

On this one I would expect NULL for object_names and object_args, as
empty does not make sense for an undefined object, still "type" should
print... "type".

Hmm ... "routine"?

I'm not sure if NULLs are better than empty arrays, but I agree that we
should pick one representation for undefined object and use it
consistently for all object types.

I think this is our chance to fix the mess. Previously (before I added
the SQL-access of the object addressing mechanism in 9.5 I mean) it
wasn't possible to get at this code at all with arbitrary input, so this
is all a "new" problem (I mean new in the last 5 years).

Obviously I failed to subtract 11 from 9.5 correctly ... I meant 4
years.

This requires a bit more work with the low-level APIs grabbing the
printed information though. And I think that the following guidelines
make sense to work on as a base definition for undefined objects:
- pg_identify_object returns the object type name, NULL for the other fields.
- pg_describe_object returns just NULL.
- pg_identify_object_as_address returns the object type name and NULL
for the other fields.

Sounds good to me.

There is some more refactoring work still needed for constraints, large
objects and functions, in a way similar to a26116c6. I am pretty happy
with the shape of 0001, so this could be applied, 0002 still needs to be
reworked so as all undefined object types behave as described above in a
consistent manner. Do those definitions make sense?

I think so, yes.

Thanks for taking care of this.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#38Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#37)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Dec 13, 2018 at 02:58:02PM -0300, Alvaro Herrera wrote:

On 2018-Dec-13, Michael Paquier wrote:

Attached is an updated version for that as 0001. Thanks for the
review. Does that part look good to you now?

+1.

Thanks for the review, I have applied this part.

Hmm ... "routine"?

That's even better.

I'm not sure if NULLs are better than empty arrays, but I agree that we
should pick one representation for undefined object and use it
consistently for all object types.

Okay, thanks.

There is some more refactoring work still needed for constraints, large
objects and functions, in a way similar to a26116c6. I am pretty happy
with the shape of 0001, so this could be applied, 0002 still needs to be
reworked so as all undefined object types behave as described above in a
consistent manner. Do those definitions make sense?

I think so, yes.

Thanks for taking care of this.

Thanks again for looking up at what was proposed. I'll see if I can
finish the refactoring part for the next CF, and be done with this
stuff.
--
Michael

#39Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#38)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

Álvaro, all,

On Fri, Dec 14, 2018 at 09:04:36AM +0900, Michael Paquier wrote:

Thanks again for looking up at what was proposed. I'll see if I can
finish the refactoring part for the next CF, and be done with this
stuff.

Please find attached an updated patch set which reworks the SQL
interface for object addresses so as NULL-ness is handled consistently
for all object types. This has needed a couple of extensions to some
current object-lookup APIs, which are added as refactoring bits
without breaking existing callers:
- Procedures and operators gain a new _extended routine which can take
a set of flags to enforce a NULL result or force qualification.
- format_type_extended needs an extra flag to enforce NULL.

On top of the refactoring issues, here is the list of changes to make
things work:
- The object type can never be NULL, meaning that for undefined
relations, routines or constraints the caller gets a generic name even
if the object is not defined. (That's the definition mentioned a
couple of messages upthread).
- Large object existence is checked with LargeObjectExists().
- Table columns show all the fields as NULL if the object identity
cannot be found instead of showing the table and its schema.

With that all the regression tests show a consistent behavior for all
object types when those are undefined. Thoughts?
--
Michael

Attachments:

0001-Add-flag-to-format_type_extended-to-enforce-NULL-nes.patchtext/x-diff; charset=us-asciiDownload
From 5f62f834ffb5f310e6469f6e7496f898cefb26ac Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 12:21:24 +0900
Subject: [PATCH 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_FORCE_NULL which returns
a NULL result instead of "??? or "-" which does not generate an error.
They will be used for future patches to improve the SQL interface for
object addresses.
---
 src/backend/utils/adt/format_type.c | 20 ++++++++++++++++----
 src/include/utils/builtins.h        |  1 +
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index f1f0ba3e0b..c41271ba4c 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -98,6 +98,9 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
@@ -116,13 +119,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -145,7 +155,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 61785a2433..8a17164bb7 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -109,6 +109,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_FORCE_NULL		0x08	/* force NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
-- 
2.20.1

0002-Refactor-format-procedure-and-operator-APIs-to-be-mo.patchtext/x-diff; charset=us-asciiDownload
From a35f6d0f42085cd4db15d9c63a23847d0cdaca96 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 13:13:28 +0900
Subject: [PATCH 2/3] Refactor format procedure and operator APIs to be more
 modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 src/include/utils/regproc.h     | 10 ++++++
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a0079821fe..e3f02b52d9 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -40,8 +40,6 @@
 #include "utils/regproc.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -322,24 +320,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -364,7 +365,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -378,7 +380,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -388,6 +390,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -746,13 +753,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -775,7 +791,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -786,7 +803,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -794,7 +811,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -804,6 +821,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -819,13 +841,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 5b9a8cbee8..3c22312a5b 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
-- 
2.20.1

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From e24c94fa48408ce626d97bb402aa3ac3c4133931 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 13:33:23 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 994 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1659 insertions(+), 320 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 102c9c8012..5cffd4b655 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 9bdbd7b60f..ffb84ac6b7 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index acffc468d2..ef43b3101c 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -364,7 +364,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -535,7 +535,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index a4acfd92da..1920d43368 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 312740c598..da2340c70a 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 7b098318ed..5843b4cccf 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 37860996a6..180cb77ca1 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17946,7 +17946,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17962,7 +17963,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17978,6 +17980,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 7dfa3278a5..7eca6f3129 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -605,11 +605,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -689,11 +689,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -785,12 +785,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -884,7 +884,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_ORIGINAL)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -907,7 +907,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -965,7 +966,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d5e30649ff..90c084d073 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -787,14 +787,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1667,7 +1673,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1698,7 +1704,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2715,10 +2721,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2728,14 +2736,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2747,14 +2760,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_FORCE_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2777,8 +2804,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2800,8 +2836,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2825,8 +2868,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2834,7 +2882,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2859,8 +2907,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2898,8 +2951,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2909,7 +2969,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -2917,19 +2977,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -2942,8 +3017,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2970,7 +3050,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2980,8 +3060,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3011,13 +3096,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3061,13 +3153,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3110,12 +3209,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3148,12 +3255,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3170,8 +3285,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3185,8 +3304,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3212,8 +3336,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3238,8 +3366,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3264,8 +3397,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3290,8 +3428,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3309,8 +3452,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3320,8 +3466,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3332,8 +3482,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3342,8 +3496,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3351,8 +3507,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3367,8 +3524,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3408,8 +3570,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3492,8 +3661,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3505,8 +3678,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3535,12 +3712,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3553,9 +3738,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3569,14 +3756,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3588,9 +3779,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3602,8 +3795,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3621,6 +3818,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3636,7 +3837,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3645,7 +3846,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3655,7 +3856,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3716,7 +3921,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3726,7 +3931,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3770,7 +3979,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3785,6 +3998,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3859,12 +4073,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		heap_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -3875,7 +4095,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -3884,8 +4104,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3929,23 +4154,31 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3959,7 +4192,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3969,11 +4202,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3989,7 +4224,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4126,6 +4362,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4133,7 +4373,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4141,7 +4382,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4188,7 +4436,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4198,7 +4446,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4216,7 +4473,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4224,7 +4482,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4241,12 +4506,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4255,11 +4521,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4281,31 +4549,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_FORCE_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4324,8 +4624,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4352,8 +4658,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4374,15 +4684,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4397,7 +4712,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4416,8 +4732,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4453,8 +4773,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4464,7 +4791,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				heap_close(attrdefDesc, AccessShareLock);
@@ -4479,8 +4807,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4490,6 +4822,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4497,11 +4831,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4514,8 +4855,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4541,7 +4886,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4550,8 +4896,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4583,13 +4933,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4637,13 +4995,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4678,14 +5044,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4705,14 +5077,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4726,8 +5104,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4744,8 +5126,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4767,8 +5153,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4790,8 +5180,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4813,8 +5207,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4836,8 +5234,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4854,7 +5256,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4868,8 +5272,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4883,8 +5291,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4896,10 +5308,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4907,11 +5323,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4926,8 +5346,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4974,8 +5398,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5037,8 +5469,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5057,8 +5493,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5078,14 +5518,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5097,11 +5543,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5114,13 +5563,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5134,11 +5587,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5157,8 +5613,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5184,20 +5646,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5207,7 +5683,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5236,7 +5716,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5245,7 +5726,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2ea05f350b..451e262fef 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -161,7 +161,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -320,7 +320,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index faf42b7640..39e5f6e634 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -572,7 +572,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1063,7 +1063,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1199,7 +1199,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1313,7 +1313,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 3e7c1067d8..608d66a1c6 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1430,10 +1430,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2092,8 +2093,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 31dcfe7b11..b80d233ede 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2826,7 +2826,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3216,7 +3216,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3256,7 +3256,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 588c1ec536..fbdc389e4c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9403,7 +9403,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9430,7 +9430,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9449,7 +9449,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9467,7 +9467,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9528,7 +9528,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e3f02b52d9..f521a2ecce 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -412,7 +412,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -422,7 +423,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -852,15 +857,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 3f95733eb1..fc420351be 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -69,14 +69,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 3c22312a5b..32deead409 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 4085e451e4..705979cefd 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -499,3 +499,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+   type   | schema | name | identity 
+----------+--------+------+----------
+ relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ table column |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+  type   | schema | name | identity 
+---------+--------+------+----------
+ routine |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | schema | name | identity 
+------------+--------+------+----------
+ constraint |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+   type   | object_names | object_args 
+----------+--------------+-------------
+ relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ table column |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+  type   | object_names | object_args 
+---------+--------------+-------------
+ routine |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | object_names | object_args 
+------------+--------------+-------------
+ constraint |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index d7df322873..51fdf12056 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -218,3 +218,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.20.1

#40Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#39)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Tue, Dec 25, 2018 at 01:34:38PM +0900, Michael Paquier wrote:

With that all the regression tests show a consistent behavior for all
object types when those are undefined. Thoughts?

End-of-commit-fest rebase and moved to next CF.
--
Michael

Attachments:

0001-Add-flag-to-format_type_extended-to-enforce-NULL-nes.patchtext/x-diff; charset=us-asciiDownload
From 6514d737cef100ea8146b1d6d10fa93e40c6ee9c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 12:21:24 +0900
Subject: [PATCH 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_FORCE_NULL which returns
a NULL result instead of "??? or "-" which does not generate an error.
They will be used for future patches to improve the SQL interface for
object addresses.
---
 src/backend/utils/adt/format_type.c | 20 ++++++++++++++++----
 src/include/utils/builtins.h        |  1 +
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 84a1b3c72b..6fc8f55668 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -98,6 +98,9 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
@@ -116,13 +119,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -145,7 +155,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 03d0ee2766..43e7ef471c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -109,6 +109,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_FORCE_NULL		0x08	/* force NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
-- 
2.20.1

0002-Refactor-format-procedure-and-operator-APIs-to-be-mo.patchtext/x-diff; charset=us-asciiDownload
From 3eaf6833c367549b9309b9df7ef7d56f8e596cc3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 13:13:28 +0900
Subject: [PATCH 2/3] Refactor format procedure and operator APIs to be more
 modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 src/include/utils/regproc.h     | 10 ++++++
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 09cf0e1abc..f85b47821a 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -40,8 +40,6 @@
 #include "utils/regproc.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -322,24 +320,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -364,7 +365,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -378,7 +380,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -388,6 +390,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -746,13 +753,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -775,7 +791,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -786,7 +803,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -794,7 +811,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -804,6 +821,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -819,13 +841,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index a0df86ebe1..c9a2a8fc4a 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
-- 
2.20.1

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From 38ebe5445dc3c22bee2263b0cd2864ac04b66bc1 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 13:33:23 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  19 +-
 src/backend/catalog/objectaddress.c          | 994 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1659 insertions(+), 320 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 0cffb69c76..5036332d59 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index cc934b54af..6d8002afeb 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 0b0d21c7f5..009aadcdcd 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -363,7 +363,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -534,7 +534,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index aa12dbe236..a8b0cca1b5 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 061527559c..b535628449 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 4c4a90f978..187e34608e 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4930ec17f6..d32d93b632 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17950,7 +17950,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17966,7 +17967,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17982,6 +17984,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2c54895831..fc9dc93bec 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -617,11 +617,11 @@ findDependentObjects(const ObjectAddress *object,
 						ReleaseDeletionLock(object);
 						return;
 					}
-					otherObjDesc = getObjectDescription(&otherObject);
+					otherObjDesc = getObjectDescription(&otherObject, false);
 					ereport(ERROR,
 							(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 							 errmsg("cannot drop %s because %s requires it",
-									getObjectDescription(object),
+									getObjectDescription(object, false),
 									otherObjDesc),
 							 errhint("You can drop %s instead.",
 									 otherObjDesc)));
@@ -701,11 +701,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -805,12 +805,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -947,7 +947,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -970,7 +970,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1028,7 +1029,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ab3ec7e356..2dc4d186c8 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -789,14 +789,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1669,7 +1675,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1700,7 +1706,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2717,10 +2723,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2730,14 +2738,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2749,14 +2762,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_FORCE_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2779,8 +2806,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2802,8 +2838,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2827,8 +2870,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2836,7 +2884,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2861,8 +2909,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2900,8 +2953,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2911,7 +2971,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -2919,19 +2979,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -2944,8 +3019,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2972,7 +3052,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2982,8 +3062,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3013,13 +3098,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3063,13 +3155,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3112,12 +3211,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3150,12 +3257,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3172,8 +3287,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3187,8 +3306,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3214,8 +3338,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3240,8 +3368,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3266,8 +3399,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3292,8 +3430,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3311,8 +3454,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3322,8 +3468,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3334,8 +3484,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3344,8 +3498,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3353,8 +3509,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3369,8 +3526,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3410,8 +3572,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3494,8 +3663,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3507,8 +3680,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3537,12 +3714,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3555,9 +3740,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3571,14 +3758,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3590,9 +3781,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3604,8 +3797,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3623,6 +3820,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3638,7 +3839,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3647,7 +3848,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3657,7 +3858,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3718,7 +3923,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3728,7 +3933,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3772,7 +3981,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3787,6 +4000,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3861,12 +4075,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -3877,7 +4097,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -3886,8 +4106,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3931,23 +4156,31 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	values[1] = PointerGetDatum(strlist_to_textarray(names));
-	nulls[1] = false;
-
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+	{
+		pfree(identity);
+
+		/* object_names */
+		values[1] = PointerGetDatum(strlist_to_textarray(names));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3961,7 +4194,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3971,11 +4204,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3991,7 +4226,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4128,6 +4364,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4135,7 +4375,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4143,7 +4384,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4190,7 +4438,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4200,7 +4448,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4218,7 +4475,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4226,7 +4484,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4243,12 +4508,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4257,11 +4523,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4283,31 +4551,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_FORCE_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4326,8 +4626,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4354,8 +4660,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4376,15 +4686,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4399,7 +4714,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4418,8 +4734,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4455,8 +4775,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4466,7 +4793,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4481,8 +4809,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4492,6 +4824,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4499,11 +4833,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4516,8 +4857,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4543,7 +4888,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4552,8 +4898,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4585,13 +4935,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4639,13 +4997,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4680,14 +5046,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4707,14 +5079,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4728,8 +5106,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4746,8 +5128,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4769,8 +5155,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4792,8 +5182,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4815,8 +5209,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4838,8 +5236,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4856,7 +5258,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4870,8 +5274,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4885,8 +5293,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4898,10 +5310,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4909,11 +5325,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4928,8 +5348,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4976,8 +5400,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5039,8 +5471,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5059,8 +5495,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5080,14 +5520,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5099,11 +5545,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5116,13 +5565,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5136,11 +5589,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5159,8 +5615,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5186,20 +5648,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5209,7 +5685,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5238,7 +5718,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5247,7 +5728,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 2b8f651c99..8c8e0a1b1d 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -319,7 +319,7 @@ changeDependencyFor(Oid classId, Oid objectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 1619c1c8a7..d1d4fb77ff 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -571,7 +571,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1062,7 +1062,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1198,7 +1198,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1312,7 +1312,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index adb77d8f69..014c6345b4 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1430,10 +1430,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2092,8 +2093,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index daf3f51636..9e85c543a8 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2835,7 +2835,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3225,7 +3225,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3265,7 +3265,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 35a9ade059..eeea058af4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9820,7 +9820,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9847,7 +9847,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9866,7 +9866,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9884,7 +9884,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9945,7 +9945,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index f85b47821a..9f777de80c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -412,7 +412,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -422,7 +423,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -852,15 +857,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6412fd5889..920669a125 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -69,14 +69,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index c9a2a8fc4a..aaf34512b7 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 4085e451e4..705979cefd 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -499,3 +499,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+   type   | schema | name | identity 
+----------+--------+------+----------
+ relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ table column |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+  type   | schema | name | identity 
+---------+--------+------+----------
+ routine |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | schema | name | identity 
+------------+--------+------+----------
+ constraint |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+   type   | object_names | object_args 
+----------+--------------+-------------
+ relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ table column |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+  type   | object_names | object_args 
+---------+--------------+-------------
+ routine |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | object_names | object_args 
+------------+--------------+-------------
+ constraint |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index d7df322873..51fdf12056 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -218,3 +218,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.20.1

#41Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#40)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Jan 31, 2019 at 04:08:18PM +0900, Michael Paquier wrote:

End-of-commit-fest rebase and moved to next CF.

Rebased version fixing some conflicts with HEAD.
--
Michael

Attachments:

0001-Add-flag-to-format_type_extended-to-enforce-NULL-nes.patchtext/x-diff; charset=us-asciiDownload
From 5844b0ba07fdd8373a7ef15a1676178cae53c40d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 12:21:24 +0900
Subject: [PATCH 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_FORCE_NULL which returns
a NULL result instead of "??? or "-" which does not generate an error.
They will be used for future patches to improve the SQL interface for
object addresses.
---
 src/backend/utils/adt/format_type.c | 20 ++++++++++++++++----
 src/include/utils/builtins.h        |  1 +
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 6ae2a31345..8ee0e10c29 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
@@ -114,13 +117,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -143,7 +153,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 03d0ee2766..43e7ef471c 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -109,6 +109,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_FORCE_NULL		0x08	/* force NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
-- 
2.20.1

0002-Refactor-format-procedure-and-operator-APIs-to-be-mo.patchtext/x-diff; charset=us-asciiDownload
From 2590b4ba96851f5ffea5f438e2e82383fdb74dae Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 13:13:28 +0900
Subject: [PATCH 2/3] Refactor format procedure and operator APIs to be more
 modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 src/include/utils/regproc.h     | 10 ++++++
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 09cf0e1abc..f85b47821a 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -40,8 +40,6 @@
 #include "utils/regproc.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 					 List **names, int *nargs, Oid *argtypes);
 
@@ -322,24 +320,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -364,7 +365,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -378,7 +380,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -388,6 +390,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -746,13 +753,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -775,7 +791,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -786,7 +803,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -794,7 +811,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -804,6 +821,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -819,13 +841,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index a0df86ebe1..c9a2a8fc4a 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
-- 
2.20.1

0003-Eliminate-user-visible-cache-lookup-errors-for-objad.patchtext/x-diff; charset=us-asciiDownload
From c57637a344b63a9e356162a17b8966271ef4d48b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 25 Dec 2018 13:33:23 +0900
Subject: [PATCH 3/3] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  30 +-
 src/backend/catalog/objectaddress.c          | 998 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   2 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  10 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   4 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1666 insertions(+), 326 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 0cffb69c76..5036332d59 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index cc934b54af..6d8002afeb 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -183,7 +183,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -260,7 +260,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 0b0d21c7f5..009aadcdcd 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -363,7 +363,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -534,7 +534,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index aa12dbe236..a8b0cca1b5 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 061527559c..b535628449 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 4c4a90f978..187e34608e 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 86ff4e5c9e..80f530f5a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17950,7 +17950,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -17966,7 +17967,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -17982,6 +17984,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2048d71535..0ae39e31b8 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -726,8 +726,8 @@ findDependentObjects(const ObjectAddress *object,
 				if (!object_address_present_add_flags(object, objflags,
 													  targetObjects))
 					elog(ERROR, "deletion of owning object %s failed to delete %s",
-						 getObjectDescription(&otherObject),
-						 getObjectDescription(object));
+						 getObjectDescription(&otherObject, false),
+						 getObjectDescription(object, false));
 
 				/* And we're done here. */
 				return;
@@ -773,11 +773,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -795,14 +795,14 @@ findDependentObjects(const ObjectAddress *object,
 		char	   *otherObjDesc;
 
 		if (OidIsValid(partitionObject.classId))
-			otherObjDesc = getObjectDescription(&partitionObject);
+			otherObjDesc = getObjectDescription(&partitionObject, false);
 		else
-			otherObjDesc = getObjectDescription(&owningObject);
+			otherObjDesc = getObjectDescription(&owningObject, false);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 				 errmsg("cannot drop %s because %s requires it",
-						getObjectDescription(object), otherObjDesc),
+						getObjectDescription(object, false), otherObjDesc),
 				 errhint("You can drop %s instead.", otherObjDesc)));
 	}
 
@@ -902,12 +902,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -1025,12 +1025,13 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			!(extra->flags & DEPFLAG_PARTITION))
 		{
 			const ObjectAddress *object = &targetObjects->refs[i];
-			char	   *otherObjDesc = getObjectDescription(&extra->dependee);
+			char	   *otherObjDesc = getObjectDescription(&extra->dependee,
+															false);
 
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because %s requires it",
-							getObjectDescription(object), otherObjDesc),
+							getObjectDescription(object, false), otherObjDesc),
 					 errhint("You can drop %s instead.", otherObjDesc)));
 		}
 	}
@@ -1078,7 +1079,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -1102,7 +1103,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1160,7 +1162,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 8b51ec7f39..0540672c46 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -789,14 +789,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 						  bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-						   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1669,7 +1675,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1700,7 +1706,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2717,10 +2723,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2730,14 +2738,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2749,14 +2762,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_FORCE_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2779,8 +2806,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2802,8 +2838,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2827,8 +2870,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2836,7 +2884,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2861,8 +2909,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2900,8 +2953,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2911,7 +2971,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -2919,19 +2979,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -2944,8 +3019,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2972,7 +3052,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -2982,8 +3062,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3013,13 +3098,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3063,13 +3155,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3112,12 +3211,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3150,12 +3257,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3172,8 +3287,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3187,8 +3306,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3214,8 +3338,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3240,8 +3368,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3266,8 +3399,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3292,8 +3430,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3311,8 +3454,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3322,8 +3468,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3334,8 +3484,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3344,8 +3498,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3353,8 +3509,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3369,8 +3526,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3410,8 +3572,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3494,8 +3663,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3507,8 +3680,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3537,12 +3714,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3555,9 +3740,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3571,14 +3758,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3590,9 +3781,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3604,8 +3797,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3623,6 +3820,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3638,7 +3839,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3647,7 +3848,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3657,7 +3858,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3718,7 +3923,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3728,7 +3933,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3772,7 +3981,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3787,6 +4000,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3861,12 +4075,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -3877,7 +4097,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -3886,8 +4106,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3931,26 +4156,34 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	if (names != NIL)
-		values[1] = PointerGetDatum(strlist_to_textarray(names));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[1] = false;
+	{
+		pfree(identity);
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_names */
+		if (names != NIL)
+			values[1] = PointerGetDatum(strlist_to_textarray(names));
+		else
+			values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3964,7 +4197,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3974,11 +4207,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -3994,7 +4229,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4131,6 +4367,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4138,7 +4378,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4146,7 +4387,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4193,7 +4441,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4203,7 +4451,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4221,7 +4478,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4229,7 +4487,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4246,12 +4511,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4260,11 +4526,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4286,31 +4554,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_FORCE_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4329,8 +4629,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4357,8 +4663,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4379,15 +4689,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4402,7 +4717,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4421,8 +4737,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4458,8 +4778,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4469,7 +4796,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4484,8 +4812,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4495,6 +4827,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4502,11 +4836,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4519,8 +4860,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4546,7 +4891,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4555,8 +4901,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4588,13 +4938,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4642,13 +5000,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4683,14 +5049,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4710,14 +5082,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4731,8 +5109,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4749,8 +5131,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4772,8 +5158,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4795,8 +5185,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4818,8 +5212,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4841,8 +5239,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4859,7 +5261,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4873,8 +5277,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4888,8 +5296,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4901,10 +5313,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4912,11 +5328,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4931,8 +5351,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -4979,8 +5403,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5042,8 +5474,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5062,8 +5498,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5083,14 +5523,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5102,11 +5548,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5119,13 +5568,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5139,11 +5592,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5162,8 +5618,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5189,20 +5651,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5212,7 +5688,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5241,7 +5721,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5250,7 +5731,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 23b01f841e..7eb8c10fe3 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 1619c1c8a7..d1d4fb77ff 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -571,7 +571,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1062,7 +1062,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1198,7 +1198,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1312,7 +1312,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index adb77d8f69..014c6345b4 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1430,10 +1430,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2092,8 +2093,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index daf3f51636..9e85c543a8 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2835,7 +2835,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3225,7 +3225,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3265,7 +3265,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 715c6a221c..32f372d361 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9843,7 +9843,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -9870,7 +9870,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9889,7 +9889,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9907,7 +9907,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -9968,7 +9968,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index f85b47821a..9f777de80c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -412,7 +412,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -422,7 +423,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -852,15 +857,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 6412fd5889..920669a125 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -69,14 +69,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 						  AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-					   List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index c9a2a8fc4a..aaf34512b7 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,10 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-					   List **objargs);
+								   List **objargs, bool missing_ok);
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-					  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 02070fd8af..cdff08511b 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -501,3 +501,711 @@ DROP SCHEMA addr_nsp CASCADE;
 NOTICE:  drop cascades to 14 other objects
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+   type   | schema | name | identity 
+----------+--------+------+----------
+ relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ table column |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+  type   | schema | name | identity 
+---------+--------+------+----------
+ routine |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | schema | name | identity 
+------------+--------+------+----------
+ constraint |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+   type   | object_names | object_args 
+----------+--------------+-------------
+ relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ table column |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+  type   | object_names | object_args 
+---------+--------------+-------------
+ routine |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | object_names | object_args 
+------------+--------------+-------------
+ constraint |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index f8ee130bfc..ac8e3bbe31 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -220,3 +220,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.20.1

#42Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#41)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Feb 21, 2019 at 04:40:13PM +0900, Michael Paquier wrote:

Rebased version fixing some conflicts with HEAD.

And rebased version for this stuff on HEAD (66c5bd3), giving visibly
v16.
--
Michael

Attachments:

v16-0001-Add-flag-to-format_type_extended-to-enforce-NULL.patchtext/x-diff; charset=us-asciiDownload
From 76ef961e02e0276af5516bdce6e0e543526db5b2 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 2 Jul 2019 16:07:06 +0900
Subject: [PATCH v16 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_FORCE_NULL which returns
a NULL result instead of "??? or "-" which does not generate an error.
They will be used for future patches to improve the SQL interface for
object addresses.
---
 src/backend/utils/adt/format_type.c | 20 ++++++++++++++++----
 src/include/utils/builtins.h        |  1 +
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 6ae2a31345..8ee0e10c29 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,6 +96,9 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
@@ -114,13 +117,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -143,7 +153,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index a3cd7d26fa..a1e418cee7 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -109,6 +109,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_FORCE_NULL		0x08	/* force NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
-- 
2.20.1

v16-0002-Refactor-format-procedure-and-operator-APIs-to-b.patchtext/x-diff; charset=us-asciiDownload
From ad8f7f57f91b243926740d5a224357f54295ee34 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 2 Jul 2019 16:07:29 +0900
Subject: [PATCH v16 2/3] Refactor format procedure and operator APIs to be
 more modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 src/include/utils/regproc.h     | 10 ++++++
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 17a7f6c9d8..8d6d73039b 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -40,8 +40,6 @@
 #include "utils/regproc.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 								 List **names, int *nargs, Oid *argtypes);
 
@@ -322,24 +320,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -364,7 +365,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -378,7 +380,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -388,6 +390,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -746,13 +753,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -775,7 +791,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -786,7 +803,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -794,7 +811,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -804,6 +821,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -819,13 +841,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 494f36121b..f76799af78 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
-- 
2.20.1

v16-0003-Eliminate-user-visible-cache-lookup-errors-for-o.patchtext/x-diff; charset=us-asciiDownload
From 4c593e00771a802bad3b11e026e10fd2e2c4615e Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 2 Jul 2019 16:22:20 +0900
Subject: [PATCH v16 3/3] Eliminate user-visible cache lookup errors for
 objaddr SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  30 +-
 src/backend/catalog/objectaddress.c          | 998 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  12 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   5 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1669 insertions(+), 328 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 0cffb69c76..5036332d59 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 2892346f80..e2b81b771f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -181,7 +181,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -258,7 +258,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 0b0d21c7f5..009aadcdcd 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -363,7 +363,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -534,7 +534,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index aa12dbe236..a8b0cca1b5 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 061527559c..b535628449 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 4c4a90f978..187e34608e 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3a8581d205..d1acb22651 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -18976,7 +18976,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -18992,7 +18993,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -19008,6 +19010,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6315fc4b2f..198abdb95c 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -730,8 +730,8 @@ findDependentObjects(const ObjectAddress *object,
 				if (!object_address_present_add_flags(object, objflags,
 													  targetObjects))
 					elog(ERROR, "deletion of owning object %s failed to delete %s",
-						 getObjectDescription(&otherObject),
-						 getObjectDescription(object));
+						 getObjectDescription(&otherObject, false),
+						 getObjectDescription(object, false));
 
 				/* And we're done here. */
 				return;
@@ -777,11 +777,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -799,14 +799,14 @@ findDependentObjects(const ObjectAddress *object,
 		char	   *otherObjDesc;
 
 		if (OidIsValid(partitionObject.classId))
-			otherObjDesc = getObjectDescription(&partitionObject);
+			otherObjDesc = getObjectDescription(&partitionObject, false);
 		else
-			otherObjDesc = getObjectDescription(&owningObject);
+			otherObjDesc = getObjectDescription(&owningObject, false);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 				 errmsg("cannot drop %s because %s requires it",
-						getObjectDescription(object), otherObjDesc),
+						getObjectDescription(object, false), otherObjDesc),
 				 errhint("You can drop %s instead.", otherObjDesc)));
 	}
 
@@ -906,12 +906,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -1029,12 +1029,13 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			!(extra->flags & DEPFLAG_PARTITION))
 		{
 			const ObjectAddress *object = &targetObjects->refs[i];
-			char	   *otherObjDesc = getObjectDescription(&extra->dependee);
+			char	   *otherObjDesc = getObjectDescription(&extra->dependee,
+															false);
 
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because %s requires it",
-							getObjectDescription(object), otherObjDesc),
+							getObjectDescription(object, false), otherObjDesc),
 					 errhint("You can drop %s instead.", otherObjDesc)));
 		}
 	}
@@ -1082,7 +1083,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -1106,7 +1107,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1164,7 +1166,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index caf48cefa9..b26efdeb20 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -789,14 +789,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 											   bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-									   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1669,7 +1675,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1700,7 +1706,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2739,10 +2745,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2752,14 +2760,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2771,14 +2784,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_FORCE_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2801,8 +2828,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2824,8 +2860,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2849,8 +2892,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2858,7 +2906,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2883,8 +2931,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2922,8 +2975,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2933,7 +2993,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -2941,19 +3001,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -2966,8 +3041,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2994,7 +3074,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -3004,8 +3084,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3035,13 +3120,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3085,13 +3177,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3134,12 +3233,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3172,12 +3279,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3194,8 +3309,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3209,8 +3328,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3236,8 +3360,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3262,8 +3390,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3288,8 +3421,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3314,8 +3452,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3333,8 +3476,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3344,8 +3490,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3356,8 +3506,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3366,8 +3520,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3375,8 +3531,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3391,8 +3548,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3432,8 +3594,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3516,8 +3685,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3529,8 +3702,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3559,12 +3736,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3577,9 +3762,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3593,14 +3780,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3612,9 +3803,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3626,8 +3819,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3645,6 +3842,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3660,7 +3861,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3669,7 +3870,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3679,7 +3880,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3740,7 +3945,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3750,7 +3955,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3794,7 +4003,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3809,6 +4022,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3883,12 +4097,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -3899,7 +4119,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -3908,8 +4128,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3953,26 +4178,34 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	if (names != NIL)
-		values[1] = PointerGetDatum(strlist_to_textarray(names));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[1] = false;
+	{
+		pfree(identity);
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_names */
+		if (names != NIL)
+			values[1] = PointerGetDatum(strlist_to_textarray(names));
+		else
+			values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3986,7 +4219,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3996,11 +4229,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -4016,7 +4251,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4153,6 +4389,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4160,7 +4400,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4168,7 +4409,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4215,7 +4463,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4225,7 +4473,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4243,7 +4500,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4251,7 +4509,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4268,12 +4533,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4282,11 +4548,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4308,31 +4576,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_FORCE_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4351,8 +4651,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4379,8 +4685,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4401,15 +4711,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4424,7 +4739,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4443,8 +4759,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4480,8 +4800,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4491,7 +4818,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4506,8 +4834,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4517,6 +4849,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4524,11 +4858,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4541,8 +4882,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4568,7 +4913,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4577,8 +4923,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4610,13 +4960,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4664,13 +5022,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4705,14 +5071,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4732,14 +5104,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4753,8 +5131,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4771,8 +5153,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4794,8 +5180,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4817,8 +5207,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4840,8 +5234,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4863,8 +5261,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4881,7 +5283,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4895,8 +5299,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4910,8 +5318,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4923,10 +5335,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4934,11 +5350,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4953,8 +5373,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -5001,8 +5425,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5064,8 +5496,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5084,8 +5520,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5105,14 +5545,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5124,11 +5570,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5141,13 +5590,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5161,11 +5614,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5184,8 +5640,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5211,20 +5673,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5234,7 +5710,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5263,7 +5743,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5272,7 +5753,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 4116e93b64..3e19759c39 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -487,7 +487,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..b4c9c06df5 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -639,7 +639,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1148,7 +1148,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1284,7 +1284,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1406,7 +1406,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index efef120c03..cf63e08a25 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1430,10 +1430,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2092,8 +2093,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 59ca5cd5a9..08b88d6b19 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2837,7 +2837,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3227,7 +3227,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3267,7 +3267,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3aee2d82ce..8ad7df81ae 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10689,7 +10689,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -10705,7 +10705,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10724,7 +10724,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10742,7 +10742,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10803,7 +10803,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
@@ -10859,7 +10859,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			  foundDep->refobjsubid != 0)
 			)
 			elog(ERROR, "found unexpected dependency for column: %s",
-				 getObjectDescription(&foundObject));
+				 getObjectDescription(&foundObject, false));
 
 		CatalogTupleDelete(depRel, &depTup->t_self);
 	}
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 8d6d73039b..f6ba417837 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -412,7 +412,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -422,7 +423,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -852,15 +857,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 95cc315868..d6936cc00a 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -69,14 +69,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 										   AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-									List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index f76799af78..f6c7b35adb 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,11 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-								   List **objargs);
+								   List **objargs, bool missing_ok);
+
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-								  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index de75b9a7dd..c2ed24d29f 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -518,3 +518,711 @@ drop cascades to function trig()
 drop cascades to function proc(integer)
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+   type   | schema | name | identity 
+----------+--------+------+----------
+ relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ table column |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+  type   | schema | name | identity 
+---------+--------+------+----------
+ routine |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | schema | name | identity 
+------------+--------+------+----------
+ constraint |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+   type   | object_names | object_args 
+----------+--------------+-------------
+ relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ table column |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+  type   | object_names | object_args 
+---------+--------------+-------------
+ routine |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | object_names | object_args 
+------------+--------------+-------------
+ constraint |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index bd94cd654b..a582623044 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -218,3 +218,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.20.1

#43Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Michael Paquier (#42)
Re: Cache lookup errors with functions manipulation object addresses

On Tue, Jul 2, 2019 at 9:28 AM Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Feb 21, 2019 at 04:40:13PM +0900, Michael Paquier wrote:

Rebased version fixing some conflicts with HEAD.

And rebased version for this stuff on HEAD (66c5bd3), giving visibly
v16.

Thanks for the patch! I couldn't check it in action, since looks like it
doesn't apply anymore [1]http://cfbot.cputube.org/patch_24_1947.log (although after a quick check I'm not entirely sure
why). Nevertheless I have a few short commentaries:

v16-0001-Add-flag-to-format_type_extended-to-enforce-NULL.patch

    - if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
    - return pstrdup("-");
    + if (type_oid == InvalidOid)
    + {
    + if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
    + return NULL;
    + else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
    + return pstrdup("-");
    + }

Here and in format_operator_extened commentary says

* Returns a palloc'd string.

but now it's possible to return NULL, so I guess comments need to be adjusted,
right?

v16-0003-Eliminate-user-visible-cache-lookup-errors-for-o.patch

    - appendStringInfo(&buffer, _("operator %s"),
    - format_operator(object->objectId));
    - break;
    + {
    + char *oprname = format_operator_extended(object->objectId,
    + FORMAT_PROC_FORCE_NULL);

Shouldn't it be FORMAT_OPERATOR_FORCE_NULL here?

I'll continue reviewing the last patch in more details.

[1]: http://cfbot.cputube.org/patch_24_1947.log

#44Michael Paquier
michael@paquier.xyz
In reply to: Dmitry Dolgov (#43)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Mon, Sep 23, 2019 at 09:15:24PM +0200, Dmitry Dolgov wrote:

Thanks for the patch! I couldn't check it in action, since looks like it
doesn't apply anymore [1] (although after a quick check I'm not entirely sure
why). Nevertheless I have a few short commentaries:

Thanks for the review. There was a small conflict in objectaddress.h
easy enough to solve.

Here and in format_operator_extened commentary says

* Returns a palloc'd string.

but now it's possible to return NULL, so I guess comments need to be adjusted,
right?

Right.

v16-0003-Eliminate-user-visible-cache-lookup-errors-for-o.patch

- appendStringInfo(&buffer, _("operator %s"),
- format_operator(object->objectId));
- break;
+ {
+ char *oprname = format_operator_extended(object->objectId,
+ FORMAT_PROC_FORCE_NULL);

Shouldn't it be FORMAT_OPERATOR_FORCE_NULL here?

Indeed, that's the case.

Please feel free to use the updated versions attached. These can
apply on top of HEAD at 30d1379.
--
Michael

Attachments:

v17-0001-Add-flag-to-format_type_extended-to-enforce-NULL.patchtext/x-diff; charset=us-asciiDownload
From dd4b4112eea12b4866aa2c6a33de57302b842c8a Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Sep 2019 08:54:37 +0900
Subject: [PATCH v17 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_FORCE_NULL which returns
a NULL result instead of "??? or "-" which does not generate an error.
They will be used for future patches to improve the SQL interface for
object addresses.
---
 src/backend/utils/adt/format_type.c | 22 +++++++++++++++++-----
 src/include/utils/builtins.h        |  1 +
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 6ae2a31345..076d2ca3f3 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,13 +96,16 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
  * Note that TYPEMOD_GIVEN is not interchangeable with "typemod == -1";
  * see the comments above for format_type().
  *
- * Returns a palloc'd string.
+ * Returns a palloc'd string, or NULL.
  */
 char *
 format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
@@ -114,13 +117,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -143,7 +153,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_FORCE_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 937ddb7ef0..78071b7755 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -109,6 +109,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_FORCE_NULL		0x08	/* force NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
-- 
2.23.0

v17-0002-Refactor-format-procedure-and-operator-APIs-to-b.patchtext/x-diff; charset=us-asciiDownload
From de4bae9cbdfb9d88c77221506f167dd4d84ebd57 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Sep 2019 08:54:52 +0900
Subject: [PATCH v17 2/3] Refactor format procedure and operator APIs to be
 more modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 src/include/utils/regproc.h     | 10 ++++++
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 17a7f6c9d8..8d6d73039b 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -40,8 +40,6 @@
 #include "utils/regproc.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 								 List **names, int *nargs, Oid *argtypes);
 
@@ -322,24 +320,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -364,7 +365,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -378,7 +380,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -388,6 +390,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -746,13 +753,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -775,7 +791,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -786,7 +803,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -794,7 +811,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -804,6 +821,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_FORCE_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -819,13 +841,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 494f36121b..f76799af78 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_FORCE_NULL		0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
-- 
2.23.0

v17-0003-Eliminate-user-visible-cache-lookup-errors-for-o.patchtext/x-diff; charset=us-asciiDownload
From 31baa56083ffc1b961445a10c0579e2b3b7be88c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 24 Sep 2019 08:57:01 +0900
Subject: [PATCH v17 3/3] Eliminate user-visible cache lookup errors for
 objaddr SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  30 +-
 src/backend/catalog/objectaddress.c          | 998 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  12 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   5 +-
 src/test/regress/expected/object_address.out | 708 +++++++++++++
 src/test/regress/sql/object_address.sql      | 124 +++
 19 files changed, 1669 insertions(+), 328 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 8edd3df643..703879316c 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 2892346f80..e2b81b771f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -181,7 +181,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -258,7 +258,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 63a2dd5cc1..46222e22db 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -356,7 +356,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -527,7 +527,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index aa12dbe236..a8b0cca1b5 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 061527559c..b535628449 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 4c4a90f978..187e34608e 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3d3d9d9183..c684518cab 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -19258,7 +19258,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -19274,7 +19275,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -19290,6 +19292,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index dd0a7d8dac..d5cccf0fe4 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -746,8 +746,8 @@ findDependentObjects(const ObjectAddress *object,
 				if (!object_address_present_add_flags(object, objflags,
 													  targetObjects))
 					elog(ERROR, "deletion of owning object %s failed to delete %s",
-						 getObjectDescription(&otherObject),
-						 getObjectDescription(object));
+						 getObjectDescription(&otherObject, false),
+						 getObjectDescription(object, false));
 
 				/* And we're done here. */
 				return;
@@ -793,11 +793,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -815,14 +815,14 @@ findDependentObjects(const ObjectAddress *object,
 		char	   *otherObjDesc;
 
 		if (OidIsValid(partitionObject.classId))
-			otherObjDesc = getObjectDescription(&partitionObject);
+			otherObjDesc = getObjectDescription(&partitionObject, false);
 		else
-			otherObjDesc = getObjectDescription(&owningObject);
+			otherObjDesc = getObjectDescription(&owningObject, false);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 				 errmsg("cannot drop %s because %s requires it",
-						getObjectDescription(object), otherObjDesc),
+						getObjectDescription(object, false), otherObjDesc),
 				 errhint("You can drop %s instead.", otherObjDesc)));
 	}
 
@@ -932,12 +932,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -1055,12 +1055,13 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			!(extra->flags & DEPFLAG_PARTITION))
 		{
 			const ObjectAddress *object = &targetObjects->refs[i];
-			char	   *otherObjDesc = getObjectDescription(&extra->dependee);
+			char	   *otherObjDesc = getObjectDescription(&extra->dependee,
+															false);
 
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because %s requires it",
-							getObjectDescription(object), otherObjDesc),
+							getObjectDescription(object, false), otherObjDesc),
 					 errhint("You can drop %s instead.", otherObjDesc)));
 		}
 	}
@@ -1108,7 +1109,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -1132,7 +1133,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1190,7 +1192,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ce8a4e927d..17273c5ede 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -789,14 +789,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 											   bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-									   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1669,7 +1675,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1700,7 +1706,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2739,10 +2745,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2752,14 +2760,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2771,14 +2784,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_FORCE_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2801,8 +2828,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					heap_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2824,8 +2860,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2849,8 +2892,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2858,7 +2906,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2883,8 +2931,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2922,8 +2975,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2933,7 +2993,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -2941,19 +3001,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_OPERATOR_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -2966,8 +3041,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2994,7 +3074,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -3004,8 +3084,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3035,13 +3120,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3085,13 +3177,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3134,12 +3233,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3172,12 +3279,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3194,8 +3309,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3209,8 +3328,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3236,8 +3360,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3262,8 +3390,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3288,8 +3421,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3314,8 +3452,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3333,8 +3476,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3344,8 +3490,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3356,8 +3506,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3366,8 +3520,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3375,8 +3531,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3391,8 +3548,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3432,8 +3594,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3516,8 +3685,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3529,8 +3702,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3559,12 +3736,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					heap_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3577,9 +3762,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3593,14 +3780,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3612,9 +3803,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3626,8 +3819,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3645,6 +3842,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3660,7 +3861,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3669,7 +3870,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3679,7 +3880,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3740,7 +3945,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3750,7 +3955,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3794,7 +4003,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3809,6 +4022,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3883,12 +4097,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -3899,7 +4119,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -3908,8 +4128,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3953,26 +4178,34 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	if (names != NIL)
-		values[1] = PointerGetDatum(strlist_to_textarray(names));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[1] = false;
+	{
+		pfree(identity);
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_names */
+		if (names != NIL)
+			values[1] = PointerGetDatum(strlist_to_textarray(names));
+		else
+			values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3986,7 +4219,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3996,11 +4229,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -4016,7 +4251,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4153,6 +4389,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4160,7 +4400,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4168,7 +4409,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4215,7 +4463,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4225,7 +4473,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		heap_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4243,7 +4500,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4251,7 +4509,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4268,12 +4533,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4282,11 +4548,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4308,31 +4576,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_FORCE_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_FORCE_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4351,8 +4651,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					heap_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4379,8 +4685,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4401,15 +4711,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4424,7 +4739,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4443,8 +4759,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4480,8 +4800,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					heap_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4491,7 +4818,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4506,8 +4834,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4517,6 +4849,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4524,11 +4858,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_FORCE_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4541,8 +4882,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4568,7 +4913,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4577,8 +4923,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4610,13 +4960,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4664,13 +5022,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					heap_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4705,14 +5071,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					heap_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4732,14 +5104,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					heap_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4753,8 +5131,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4771,8 +5153,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4794,8 +5180,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4817,8 +5207,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4840,8 +5234,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4863,8 +5261,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4881,7 +5283,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4895,8 +5299,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4910,8 +5318,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4923,10 +5335,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4934,11 +5350,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4953,8 +5373,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -5001,8 +5425,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					heap_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5064,8 +5496,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5084,8 +5520,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5105,14 +5545,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					heap_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5124,11 +5570,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5141,13 +5590,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5161,11 +5614,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5184,8 +5640,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					heap_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5211,20 +5673,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5234,7 +5710,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5263,7 +5743,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5272,7 +5753,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index a060c25d2e..f50c39aa0c 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -487,7 +487,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..b4c9c06df5 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -639,7 +639,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1148,7 +1148,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1284,7 +1284,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1406,7 +1406,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index f7ee9838f7..c008f7eec5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1429,10 +1429,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2091,8 +2092,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index f7202cc9e7..aed98974a2 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2853,7 +2853,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3243,7 +3243,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3283,7 +3283,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 05593f3316..e63e262e9d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10743,7 +10743,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -10759,7 +10759,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10778,7 +10778,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10796,7 +10796,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10857,7 +10857,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
@@ -10913,7 +10913,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			  foundDep->refobjsubid != 0)
 			)
 			elog(ERROR, "found unexpected dependency for column: %s",
-				 getObjectDescription(&foundObject));
+				 getObjectDescription(&foundObject, false));
 
 		CatalogTupleDelete(depRel, &depTup->t_self);
 	}
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 8d6d73039b..f6ba417837 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -412,7 +412,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -422,7 +423,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -852,15 +857,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 7e61569f9f..fb798f9e3e 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -69,14 +69,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 										   AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-									List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern struct ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index f76799af78..f6c7b35adb 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,11 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-								   List **objargs);
+								   List **objargs, bool missing_ok);
+
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-								  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..2dc5a8662d 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -521,3 +521,711 @@ drop cascades to function trig()
 drop cascades to function proc(integer)
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+   type   | schema | name | identity 
+----------+--------+------+----------
+ relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ table column |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+  type   | schema | name | identity 
+---------+--------+------+----------
+ routine |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+ type | schema | name | identity 
+------+--------+------+----------
+ type |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+ type | schema | name | identity 
+------+--------+------+----------
+ cast |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ collation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | schema | name | identity 
+------------+--------+------+----------
+ constraint |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | schema | name | identity 
+------------+--------+------+----------
+ conversion |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ default value |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+   type   | schema | name | identity 
+----------+--------+------+----------
+ language |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ large object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+   type   | schema | name | identity 
+----------+--------+------+----------
+ operator |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | schema | name | identity 
+----------------+--------+------+----------
+ operator class |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | schema | name | identity 
+-----------------+--------+------+----------
+ operator family |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ operator of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ function of access method |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | schema | name | identity 
+------+--------+------+----------
+ rule |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | schema | name | identity 
+---------+--------+------+----------
+ trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | schema | name | identity 
+--------+--------+------+----------
+ schema |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | schema | name | identity 
+-------------------+--------+------+----------
+ statistics object |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | schema | name | identity 
+--------------------+--------+------+----------
+ text search parser |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | schema | name | identity 
+------------------------+--------+------+----------
+ text search dictionary |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ text search template |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | schema | name | identity 
+---------------------------+--------+------+----------
+ text search configuration |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+ type | schema | name | identity 
+------+--------+------+----------
+ role |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+   type   | schema | name | identity 
+----------+--------+------+----------
+ database |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | schema | name | identity 
+------------+--------+------+----------
+ tablespace |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ foreign-data wrapper |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | schema | name | identity 
+--------+--------+------+----------
+ server |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ user mapping |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ default acl |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ extension |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | schema | name | identity 
+---------------+--------+------+----------
+ event trigger |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+  type  | schema | name | identity 
+--------+--------+------+----------
+ policy |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+    type     | schema | name | identity 
+-------------+--------+------+----------
+ publication |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | schema | name | identity 
+----------------------+--------+------+----------
+ publication relation |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | schema | name | identity 
+--------------+--------+------+----------
+ subscription |        |      | 
+(1 row)
+
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | schema | name | identity 
+-----------+--------+------+----------
+ transform |        |      | 
+(1 row)
+
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+ pg_describe_object 
+--------------------
+ 
+(1 row)
+
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+   type   | object_names | object_args 
+----------+--------------+-------------
+ relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ table column |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+  type   | object_names | object_args 
+---------+--------------+-------------
+ routine |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+ type | object_names | object_args 
+------+--------------+-------------
+ type |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+ type | object_names | object_args 
+------+--------------+-------------
+ cast |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ collation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+    type    | object_names | object_args 
+------------+--------------+-------------
+ constraint |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+    type    | object_names | object_args 
+------------+--------------+-------------
+ conversion |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ default value |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+   type   | object_names | object_args 
+----------+--------------+-------------
+ language |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ large object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+   type   | object_names | object_args 
+----------+--------------+-------------
+ operator |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+      type      | object_names | object_args 
+----------------+--------------+-------------
+ operator class |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+      type       | object_names | object_args 
+-----------------+--------------+-------------
+ operator family |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ operator of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ function of access method |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+ type | object_names | object_args 
+------+--------------+-------------
+ rule |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+  type   | object_names | object_args 
+---------+--------------+-------------
+ trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+  type  | object_names | object_args 
+--------+--------------+-------------
+ schema |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+       type        | object_names | object_args 
+-------------------+--------------+-------------
+ statistics object |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+        type        | object_names | object_args 
+--------------------+--------------+-------------
+ text search parser |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+          type          | object_names | object_args 
+------------------------+--------------+-------------
+ text search dictionary |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ text search template |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+           type            | object_names | object_args 
+---------------------------+--------------+-------------
+ text search configuration |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+ type | object_names | object_args 
+------+--------------+-------------
+ role |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+   type   | object_names | object_args 
+----------+--------------+-------------
+ database |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+    type    | object_names | object_args 
+------------+--------------+-------------
+ tablespace |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ foreign-data wrapper |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+  type  | object_names | object_args 
+--------+--------------+-------------
+ server |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ user mapping |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ default acl |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ extension |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+     type      | object_names | object_args 
+---------------+--------------+-------------
+ event trigger |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+  type  | object_names | object_args 
+--------+--------------+-------------
+ policy |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+    type     | object_names | object_args 
+-------------+--------------+-------------
+ publication |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+         type         | object_names | object_args 
+----------------------+--------------+-------------
+ publication relation |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+     type     | object_names | object_args 
+--------------+--------------+-------------
+ subscription |              | 
+(1 row)
+
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
+   type    | object_names | object_args 
+-----------+--------------+-------------
+ transform |              | 
+(1 row)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..7e923b154a 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -221,3 +221,127 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Keep those checks in the same order as getObjectIdentityParts()
+SELECT * FROM pg_identify_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectDescription()
+SELECT pg_describe_object('pg_class'::regclass, 0, 0); -- no relation
+SELECT pg_describe_object('pg_class'::regclass, 'pg_class'::regclass, -8); -- no column for relation
+SELECT pg_describe_object('pg_proc'::regclass, 0, 0); -- no function
+SELECT pg_describe_object('pg_type'::regclass, 0, 0); -- no type
+SELECT pg_describe_object('pg_cast'::regclass, 0, 0); -- no cast
+SELECT pg_describe_object('pg_collation'::regclass, 0, 0); -- no collation
+SELECT pg_describe_object('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT pg_describe_object('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT pg_describe_object('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT pg_describe_object('pg_language'::regclass, 0, 0); -- no language
+SELECT pg_describe_object('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT pg_describe_object('pg_operator'::regclass, 0, 0); -- no operator
+SELECT pg_describe_object('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT pg_describe_object('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT pg_describe_object('pg_am'::regclass, 0, 0); -- no access method
+SELECT pg_describe_object('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT pg_describe_object('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT pg_describe_object('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT pg_describe_object('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT pg_describe_object('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT pg_describe_object('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT pg_describe_object('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT pg_describe_object('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT pg_describe_object('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT pg_describe_object('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT pg_describe_object('pg_authid'::regclass, 0, 0); -- no role
+SELECT pg_describe_object('pg_database'::regclass, 0, 0); -- no database
+SELECT pg_describe_object('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT pg_describe_object('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT pg_describe_object('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT pg_describe_object('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT pg_describe_object('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT pg_describe_object('pg_extension'::regclass, 0, 0); -- no extension
+SELECT pg_describe_object('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT pg_describe_object('pg_policy'::regclass, 0, 0); -- no policy
+SELECT pg_describe_object('pg_publication'::regclass, 0, 0); -- no publication
+SELECT pg_describe_object('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT pg_describe_object('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT pg_describe_object('pg_transform'::regclass, 0, 0); -- no transformation
+-- Keep those checks in the same order as getObjectTypeDescription()
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 0, 0); -- no relation
+SELECT * FROM pg_identify_object_as_address('pg_class'::regclass, 'pg_class'::regclass, 100); -- no column for relation
+SELECT * FROM pg_identify_object_as_address('pg_proc'::regclass, 0, 0); -- no function
+SELECT * FROM pg_identify_object_as_address('pg_type'::regclass, 0, 0); -- no type
+SELECT * FROM pg_identify_object_as_address('pg_cast'::regclass, 0, 0); -- no cast
+SELECT * FROM pg_identify_object_as_address('pg_collation'::regclass, 0, 0); -- no collation
+SELECT * FROM pg_identify_object_as_address('pg_constraint'::regclass, 0, 0); -- no constraint
+SELECT * FROM pg_identify_object_as_address('pg_conversion'::regclass, 0, 0); -- no conversion
+SELECT * FROM pg_identify_object_as_address('pg_attrdef'::regclass, 0, 0); -- no default attribute
+SELECT * FROM pg_identify_object_as_address('pg_language'::regclass, 0, 0); -- no language
+SELECT * FROM pg_identify_object_as_address('pg_largeobject'::regclass, 0, 0); -- no large object, no error
+SELECT * FROM pg_identify_object_as_address('pg_operator'::regclass, 0, 0); -- no operator
+SELECT * FROM pg_identify_object_as_address('pg_opclass'::regclass, 0, 0); -- no opclass, no need to check for no access method
+SELECT * FROM pg_identify_object_as_address('pg_opfamily'::regclass, 0, 0); -- no opfamily
+SELECT * FROM pg_identify_object_as_address('pg_am'::regclass, 0, 0); -- no access method
+SELECT * FROM pg_identify_object_as_address('pg_amop'::regclass, 0, 0); -- no AM operator
+SELECT * FROM pg_identify_object_as_address('pg_amproc'::regclass, 0, 0); -- no AM proc
+SELECT * FROM pg_identify_object_as_address('pg_rewrite'::regclass, 0, 0); -- no rewrite
+SELECT * FROM pg_identify_object_as_address('pg_trigger'::regclass, 0, 0); -- no trigger
+SELECT * FROM pg_identify_object_as_address('pg_namespace'::regclass, 0, 0); -- no schema
+SELECT * FROM pg_identify_object_as_address('pg_statistic_ext'::regclass, 0, 0); -- no statistics
+SELECT * FROM pg_identify_object_as_address('pg_ts_parser'::regclass, 0, 0); -- no TS parser
+SELECT * FROM pg_identify_object_as_address('pg_ts_dict'::regclass, 0, 0); -- no TS dictionnary
+SELECT * FROM pg_identify_object_as_address('pg_ts_template'::regclass, 0, 0); -- no TS template
+SELECT * FROM pg_identify_object_as_address('pg_ts_config'::regclass, 0, 0); -- no TS configuration
+SELECT * FROM pg_identify_object_as_address('pg_authid'::regclass, 0, 0); -- no role
+SELECT * FROM pg_identify_object_as_address('pg_database'::regclass, 0, 0); -- no database
+SELECT * FROM pg_identify_object_as_address('pg_tablespace'::regclass, 0, 0); -- no tablespace
+SELECT * FROM pg_identify_object_as_address('pg_foreign_data_wrapper'::regclass, 0, 0); -- no FDW
+SELECT * FROM pg_identify_object_as_address('pg_foreign_server'::regclass, 0, 0); -- no server
+SELECT * FROM pg_identify_object_as_address('pg_user_mapping'::regclass, 0, 0); -- no user mapping
+SELECT * FROM pg_identify_object_as_address('pg_default_acl'::regclass, 0, 0); -- no default ACL
+SELECT * FROM pg_identify_object_as_address('pg_extension'::regclass, 0, 0); -- no extension
+SELECT * FROM pg_identify_object_as_address('pg_event_trigger'::regclass, 0, 0); -- no event trigger
+SELECT * FROM pg_identify_object_as_address('pg_policy'::regclass, 0, 0); -- no policy
+SELECT * FROM pg_identify_object_as_address('pg_publication'::regclass, 0, 0); -- no publication
+SELECT * FROM pg_identify_object_as_address('pg_publication_rel'::regclass, 0, 0); -- no publication relation
+SELECT * FROM pg_identify_object_as_address('pg_subscription'::regclass, 0, 0); -- no subscription
+SELECT * FROM pg_identify_object_as_address('pg_transform'::regclass, 0, 0); -- no transformation
-- 
2.23.0

#45Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Michael Paquier (#44)
Re: Cache lookup errors with functions manipulation object addresses

At Tue, 24 Sep 2019 08:58:50 +0900, Michael Paquier <michael@paquier.xyz> wrote in <20190923235850.GA2012@paquier.xyz>

On Mon, Sep 23, 2019 at 09:15:24PM +0200, Dmitry Dolgov wrote:
Please feel free to use the updated versions attached. These can
apply on top of HEAD at 30d1379.

In 0003, empty string and NULL are not digtinguishable in psql
text output. It'd be better that the regression test checks that
the return is actually NULL using "is NULL" or "\pset null
'<null>'" or something like.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#46Michael Paquier
michael@paquier.xyz
In reply to: Kyotaro Horiguchi (#45)
Re: Cache lookup errors with functions manipulation object addresses

On Tue, Sep 24, 2019 at 03:25:08PM +0900, Kyotaro Horiguchi wrote:

In 0003, empty string and NULL are not digtinguishable in psql
text output. It'd be better that the regression test checks that
the return is actually NULL using "is NULL" or "\pset null
'<null>'" or something like.

For a patch whose purpose is to check after NULL, I missed to consider
that... Thanks! I have just added a "\pset null NULL" because that's
less bulky than the other option, and all the results properly show
NULL, without any empty strings around. I am not sending a new patch
set just for that change though, and the most recent version is here:
https://github.com/michaelpq/postgres/tree/objaddr_nulls
--
Michael

#47Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#46)
Re: Cache lookup errors with functions manipulation object addresses

On 2019-Sep-25, Michael Paquier wrote:

On Tue, Sep 24, 2019 at 03:25:08PM +0900, Kyotaro Horiguchi wrote:

In 0003, empty string and NULL are not digtinguishable in psql
text output. It'd be better that the regression test checks that
the return is actually NULL using "is NULL" or "\pset null
'<null>'" or something like.

For a patch whose purpose is to check after NULL, I missed to consider
that... Thanks! I have just added a "\pset null NULL" because that's
less bulky than the other option, and all the results properly show
NULL, without any empty strings around. I am not sending a new patch
set just for that change though, and the most recent version is here:
https://github.com/michaelpq/postgres/tree/objaddr_nulls

I have still to review this comprehensively, but one thing I don't quite
like is the shape of the new regression tests, which seem a bit too
bulky ... why not use the style elsewhere in that file, with a large
VALUES clause to supply input params for a single query? That would be
a lot faster, too.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#48Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#44)
Re: Cache lookup errors with functions manipulation object addresses

On 2019-Sep-24, Michael Paquier wrote:

+ * - FORMAT_TYPE_FORCE_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such

I think FORCE_NULL is a strange name for this flag (and its two
siblings); I think something like FORMAT_TYPE_INVALID_AS_NULL is
clearer.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#49Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#48)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, Sep 25, 2019 at 09:21:03AM -0300, Alvaro Herrera wrote:

On 2019-Sep-24, Michael Paquier wrote:

+ * - FORMAT_TYPE_FORCE_NULL
+ *	  if the type OID is invalid or unknown, return NULL instead of ???
+ *	  or such

I think FORCE_NULL is a strange name for this flag (and its two
siblings); I think something like FORMAT_TYPE_INVALID_AS_NULL is
clearer.

Good idea. I see the point. Patches 0001 and 0002 are an easy cut.
For patch 0003, we could also argue if we'd want another flavor of
getObjectIdentity() (an "extended" version) which takes an extra
argument so as we reduce the size of the patch as there would be no
need to update all the existing callers (note: I still think no on
this one).

I have still to review this comprehensively, but one thing I don't quite
like is the shape of the new regression tests, which seem a bit too
bulky ... why not use the style elsewhere in that file, with a large
VALUES clause to supply input params for a single query? That would be
a lot faster, too.

That makes sense. Here is how I would write it then:
WITH objects (classid, objid, objsubid) AS (VALUES
('pg_class'::regclass, 0, 0), -- no relation
[ ... ]
)
SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
AS ident,
ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
AS addr,
pg_describe_object(objects.classid, objects.objid, objects.objsubid)
AS descr,
FROM objects
ORDER BY objects.classid, objects.objid, objects.objsubid;
--
Michael

#50Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Michael Paquier (#44)
Re: Cache lookup errors with functions manipulation object addresses

On Tue, Sep 24, 2019 at 1:58 AM Michael Paquier <michael@paquier.xyz> wrote:

Please feel free to use the updated versions attached. These can
apply on top of HEAD at 30d1379.

Thanks for the update. Looking at v17 0003 I have one more question. In all the
places where we have to do systable_endscan, it followed by heap_close,
although in the rest of the file table_close is used. I guess this logic is not
heap specific and should also use table_close?

#51Michael Paquier
michael@paquier.xyz
In reply to: Dmitry Dolgov (#50)
Re: Cache lookup errors with functions manipulation object addresses

On Wed, Oct 16, 2019 at 01:04:57PM +0200, Dmitry Dolgov wrote:

Thanks for the update. Looking at v17 0003 I have one more question. In all the
places where we have to do systable_endscan, it followed by heap_close,
although in the rest of the file table_close is used. I guess this logic is not
heap specific and should also use table_close?

We need to use table_close as we cannot assume that the relation will
always be opened for heap. This patch set is around for so long, so
that was a rotten part still able to compile as we have a macro to
cover the gap. Thanks for noticing that. I am sending an updated
patch set in a but, Alvaro had more comments.
--
Michael

#52Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#49)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Sep 26, 2019 at 03:52:03PM +0900, Michael Paquier wrote:

On Wed, Sep 25, 2019 at 09:21:03AM -0300, Alvaro Herrera wrote:

On 2019-Sep-24, Michael Paquier wrote:

+ * - FORMAT_TYPE_FORCE_NULL
+ *	  if the type OID is invalid or unknown, return NULL instead of ???
+ *	  or such

I think FORCE_NULL is a strange name for this flag (and its two
siblings); I think something like FORMAT_TYPE_INVALID_AS_NULL is
clearer.

Good idea. I see the point.

Got to think more on this one, and your idea is better. So changed
this way.

I have still to review this comprehensively, but one thing I don't quite
like is the shape of the new regression tests, which seem a bit too
bulky ... why not use the style elsewhere in that file, with a large
VALUES clause to supply input params for a single query? That would be
a lot faster, too.

That makes sense. Here is how I would write it then:
WITH objects (classid, objid, objsubid) AS (VALUES
('pg_class'::regclass, 0, 0), -- no relation
[ ... ]
)
SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
AS ident,
ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
AS addr,
pg_describe_object(objects.classid, objects.objid, objects.objsubid)
AS descr,
FROM objects
ORDER BY objects.classid, objects.objid, objects.objsubid;

That's actually cleaner, so changed as well. Attached is an updated
patch set with the heap_close() calls removed as per the recent report
from Dmitry.
--
Michael

Attachments:

v18-0001-Add-flag-to-format_type_extended-to-enforce-NULL.patchtext/x-diff; charset=us-asciiDownload
From 3d42d9faf77bf6345694aaee8ce5e2317238d362 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 17 Oct 2019 10:25:24 +0900
Subject: [PATCH v18 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_INVALID_AS_NULL which
returns a NULL result instead of "??? or "-" which does not generate an
error. They will be used for future patches to improve the SQL interface
for object addresses.
---
 src/backend/utils/adt/format_type.c | 22 +++++++++++++++++-----
 src/include/utils/builtins.h        |  1 +
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index 6ae2a31345..8696b11a9e 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,13 +96,16 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_INVALID_AS_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
  * Note that TYPEMOD_GIVEN is not interchangeable with "typemod == -1";
  * see the comments above for format_type().
  *
- * Returns a palloc'd string.
+ * Returns a palloc'd string, or NULL.
  */
 char *
 format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
@@ -114,13 +117,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -143,7 +153,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 937ddb7ef0..44298d7410 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -109,6 +109,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_INVALID_AS_NULL	0x08	/* NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
-- 
2.23.0

v18-0002-Refactor-format-procedure-and-operator-APIs-to-b.patchtext/x-diff; charset=us-asciiDownload
From 76bcbf05ec91daf82307cd2bde1ca7cf32b5e272 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 17 Oct 2019 10:25:39 +0900
Subject: [PATCH v18 2/3] Refactor format procedure and operator APIs to be
 more modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 src/include/utils/regproc.h     | 10 ++++++
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 17a7f6c9d8..ee0358ac70 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -40,8 +40,6 @@
 #include "utils/regproc.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 								 List **names, int *nargs, Oid *argtypes);
 
@@ -322,24 +320,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_INVALID_AS_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -364,7 +365,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -378,7 +380,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -388,6 +390,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_INVALID_AS_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -746,13 +753,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_INVALID_AS_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -775,7 +791,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -786,7 +803,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -794,7 +811,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -804,6 +821,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_INVALID_AS_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -819,13 +841,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 494f36121b..72ca529ff8 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_INVALID_AS_NULL	0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_INVALID_AS_NULL	0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
-- 
2.23.0

v18-0003-Eliminate-user-visible-cache-lookup-errors-for-o.patchtext/x-diff; charset=us-asciiDownload
From 546d568922d3b55c6269871e67cc2ba1b7ab4ef3 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 17 Oct 2019 10:35:15 +0900
Subject: [PATCH v18 3/3] Eliminate user-visible cache lookup errors for
 objaddr SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 doc/src/sgml/func.sgml                       |   7 +-
 src/backend/catalog/dependency.c             |  30 +-
 src/backend/catalog/objectaddress.c          | 998 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  12 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   5 +-
 src/test/regress/expected/object_address.out |  98 ++
 src/test/regress/sql/object_address.sql      |  56 ++
 19 files changed, 991 insertions(+), 328 deletions(-)

diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 8edd3df643..703879316c 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 2892346f80..e2b81b771f 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -181,7 +181,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -258,7 +258,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 63a2dd5cc1..46222e22db 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -356,7 +356,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -527,7 +527,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index aa12dbe236..a8b0cca1b5 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -81,7 +81,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -115,7 +115,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -165,7 +165,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -180,7 +180,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -205,7 +205,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -293,7 +293,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -325,7 +325,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 061527559c..b535628449 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -103,7 +103,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -147,7 +147,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -179,7 +179,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -223,7 +223,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -289,7 +289,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -451,7 +451,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -473,7 +473,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -504,7 +504,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -545,7 +545,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -656,7 +656,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 4c4a90f978..187e34608e 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -124,7 +124,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -149,7 +149,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -187,7 +187,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0aa399dc2f..83bf28ba87 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -19342,7 +19342,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    This description is intended to be human-readable, and might be translated,
    depending on server configuration.
    This is useful to determine the identity of an object as stored in the
-   <structname>pg_depend</structname> catalog.
+   <structname>pg_depend</structname> catalog. This function returns
+   <literal>NULL</literal> values for undefined objects.
   </para>
 
   <para>
@@ -19358,7 +19359,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
    uniquely identify the object, otherwise <literal>NULL</literal>;
    <parameter>identity</parameter> is the complete object identity, with the
    precise format depending on object type, and each name within the format
-   being schema-qualified and quoted as necessary.
+   being schema-qualified and quoted as necessary. Undefined objects are
+   identified with <literal>NULL</literal> values.
   </para>
 
   <para>
@@ -19374,6 +19376,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
    <function>pg_get_object_address</function> to obtain the internal address
    of the object.
    This function is the inverse of <function>pg_get_object_address</function>.
+   Undefined objects are identified with <literal>NULL</literal> values.
   </para>
 
   <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 03582781f6..9af60f87a2 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -746,8 +746,8 @@ findDependentObjects(const ObjectAddress *object,
 				if (!object_address_present_add_flags(object, objflags,
 													  targetObjects))
 					elog(ERROR, "deletion of owning object %s failed to delete %s",
-						 getObjectDescription(&otherObject),
-						 getObjectDescription(object));
+						 getObjectDescription(&otherObject, false),
+						 getObjectDescription(object, false));
 
 				/* And we're done here. */
 				return;
@@ -793,11 +793,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -815,14 +815,14 @@ findDependentObjects(const ObjectAddress *object,
 		char	   *otherObjDesc;
 
 		if (OidIsValid(partitionObject.classId))
-			otherObjDesc = getObjectDescription(&partitionObject);
+			otherObjDesc = getObjectDescription(&partitionObject, false);
 		else
-			otherObjDesc = getObjectDescription(&owningObject);
+			otherObjDesc = getObjectDescription(&owningObject, false);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 				 errmsg("cannot drop %s because %s requires it",
-						getObjectDescription(object), otherObjDesc),
+						getObjectDescription(object, false), otherObjDesc),
 				 errhint("You can drop %s instead.", otherObjDesc)));
 	}
 
@@ -932,12 +932,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -1055,12 +1055,13 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			!(extra->flags & DEPFLAG_PARTITION))
 		{
 			const ObjectAddress *object = &targetObjects->refs[i];
-			char	   *otherObjDesc = getObjectDescription(&extra->dependee);
+			char	   *otherObjDesc = getObjectDescription(&extra->dependee,
+															false);
 
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because %s requires it",
-							getObjectDescription(object), otherObjDesc),
+							getObjectDescription(object, false), otherObjDesc),
 					 errhint("You can drop %s instead.", otherObjDesc)));
 		}
 	}
@@ -1108,7 +1109,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -1132,7 +1133,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1190,7 +1192,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ce8a4e927d..fcbbc8c3fc 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -789,14 +789,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 											   bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-									   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1669,7 +1675,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1700,7 +1706,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2739,10 +2745,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2752,14 +2760,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2771,14 +2784,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_INVALID_AS_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_INVALID_AS_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2801,8 +2828,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					table_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2824,8 +2860,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2849,8 +2892,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2858,7 +2906,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2883,8 +2931,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2922,8 +2975,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					table_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -2933,7 +2993,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -2941,19 +3001,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_OPERATOR_INVALID_AS_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -2966,8 +3041,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -2994,7 +3074,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -3004,8 +3084,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3035,13 +3120,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3085,13 +3177,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3134,12 +3233,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3172,12 +3279,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					table_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3194,8 +3309,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3209,8 +3328,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3236,8 +3360,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3262,8 +3390,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3288,8 +3421,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3314,8 +3452,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3333,8 +3476,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3344,8 +3490,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3356,8 +3506,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3366,8 +3520,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3375,8 +3531,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3391,8 +3548,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3432,8 +3594,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3516,8 +3685,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3529,8 +3702,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3559,12 +3736,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					table_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3577,9 +3762,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3593,14 +3780,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3612,9 +3803,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3626,8 +3819,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3645,6 +3842,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3660,7 +3861,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3669,7 +3870,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3679,7 +3880,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3740,7 +3945,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3750,7 +3955,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3794,7 +4003,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3809,6 +4022,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3883,12 +4097,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -3899,7 +4119,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -3908,8 +4128,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3953,26 +4178,34 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	if (names != NIL)
-		values[1] = PointerGetDatum(strlist_to_textarray(names));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[1] = false;
+	{
+		pfree(identity);
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_names */
+		if (names != NIL)
+			values[1] = PointerGetDatum(strlist_to_textarray(names));
+		else
+			values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -3986,7 +4219,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -3996,11 +4229,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -4016,7 +4251,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4153,6 +4389,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4160,7 +4400,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4168,7 +4409,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4215,7 +4463,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4225,7 +4473,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		table_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4243,7 +4500,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4251,7 +4509,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4268,12 +4533,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4282,11 +4548,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4308,31 +4576,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_INVALID_AS_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4351,8 +4651,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					table_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4379,8 +4685,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4401,15 +4711,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4424,7 +4739,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4443,8 +4759,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4480,8 +4800,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					table_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4491,7 +4818,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4506,8 +4834,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4517,6 +4849,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4524,11 +4858,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_INVALID_AS_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4541,8 +4882,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4568,7 +4913,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4577,8 +4923,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4610,13 +4960,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4664,13 +5022,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4705,14 +5071,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					table_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4732,14 +5104,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					table_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4753,8 +5131,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4771,8 +5153,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4794,8 +5180,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4817,8 +5207,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4840,8 +5234,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4863,8 +5261,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4881,7 +5283,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -4895,8 +5299,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -4910,8 +5318,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -4923,10 +5335,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -4934,11 +5350,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -4953,8 +5373,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -5001,8 +5425,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5064,8 +5496,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5084,8 +5520,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5105,14 +5545,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					table_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5124,11 +5570,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5141,13 +5590,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5161,11 +5614,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5184,8 +5640,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					table_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5211,20 +5673,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5234,7 +5710,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5263,7 +5743,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5272,7 +5753,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index a060c25d2e..f50c39aa0c 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -487,7 +487,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index fb7f8ddefc..b4c9c06df5 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -639,7 +639,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1148,7 +1148,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1284,7 +1284,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1406,7 +1406,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index f7ee9838f7..c008f7eec5 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1429,10 +1429,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -2091,8 +2092,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index f7202cc9e7..aed98974a2 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2853,7 +2853,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3243,7 +3243,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3283,7 +3283,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8d25d14772..5f8339c412 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10774,7 +10774,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -10790,7 +10790,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10809,7 +10809,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10827,7 +10827,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -10888,7 +10888,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
@@ -10944,7 +10944,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			  foundDep->refobjsubid != 0)
 			)
 			elog(ERROR, "found unexpected dependency for column: %s",
-				 getObjectDescription(&foundObject));
+				 getObjectDescription(&foundObject, false));
 
 		CatalogTupleDelete(depRel, &depTup->t_self);
 	}
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index ee0358ac70..a4a4714b8c 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -412,7 +412,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -422,7 +423,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -852,15 +857,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 7e61569f9f..fb798f9e3e 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -69,14 +69,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 										   AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-									List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern struct ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 72ca529ff8..ce1492be26 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,11 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-								   List **objargs);
+								   List **objargs, bool missing_ok);
+
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-								  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..de43fd5381 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -521,3 +521,101 @@ drop cascades to function trig()
 drop cascades to function proc(integer)
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Keep this list in the same order as getObjectIdentityParts()
+WITH objects (classid, objid, objsubid) AS (VALUES
+    ('pg_class'::regclass, 0, 0), -- no relation
+    ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+    ('pg_proc'::regclass, 0, 0), -- no function
+    ('pg_type'::regclass, 0, 0), -- no type
+    ('pg_cast'::regclass, 0, 0), -- no cast
+    ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_constraint'::regclass, 0, 0), -- no constraint
+    ('pg_conversion'::regclass, 0, 0), -- no conversion
+    ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+    ('pg_language'::regclass, 0, 0), -- no language
+    ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+    ('pg_operator'::regclass, 0, 0), -- no operator
+    ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+    ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+    ('pg_am'::regclass, 0, 0), -- no access method
+    ('pg_amop'::regclass, 0, 0), -- no AM operator
+    ('pg_amproc'::regclass, 0, 0), -- no AM proc
+    ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+    ('pg_trigger'::regclass, 0, 0), -- no trigger
+    ('pg_namespace'::regclass, 0, 0), -- no schema
+    ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+    ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+    ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+    ('pg_ts_template'::regclass, 0, 0), -- no TS template
+    ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+    ('pg_authid'::regclass, 0, 0), -- no role
+    ('pg_database'::regclass, 0, 0), -- no database
+    ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+    ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+    ('pg_foreign_server'::regclass, 0, 0), -- no server
+    ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+    ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+    ('pg_extension'::regclass, 0, 0), -- no extension
+    ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+    ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_publication'::regclass, 0, 0), -- no publication
+    ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+    ('pg_subscription'::regclass, 0, 0), -- no subscription
+    ('pg_transform'::regclass, 0, 0) -- no transformation
+  )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+         AS ident,
+       ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+         AS addr,
+       pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+         AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
+                 ident                  |                 addr                  | descr 
+----------------------------------------+---------------------------------------+-------
+ ("(""default acl"",,,)")               | ("(""default acl"",,)")               | NULL
+ ("(tablespace,,,)")                    | ("(tablespace,,)")                    | NULL
+ ("(type,,,)")                          | ("(type,,)")                          | NULL
+ ("(routine,,,)")                       | ("(routine,,)")                       | NULL
+ ("(relation,,,)")                      | ("(relation,,)")                      | NULL
+ ("(""table column"",,,)")              | ("(""table column"",,)")              | NULL
+ ("(role,,,)")                          | ("(role,,)")                          | NULL
+ ("(database,,,)")                      | ("(database,,)")                      | NULL
+ ("(server,,,)")                        | ("(server,,)")                        | NULL
+ ("(""user mapping"",,,)")              | ("(""user mapping"",,)")              | NULL
+ ("(""foreign-data wrapper"",,,)")      | ("(""foreign-data wrapper"",,)")      | NULL
+ ("(""access method"",,,)")             | ("(""access method"",,)")             | NULL
+ ("(""operator of access method"",,,)") | ("(""operator of access method"",,)") | NULL
+ ("(""function of access method"",,,)") | ("(""function of access method"",,)") | NULL
+ ("(""default value"",,,)")             | ("(""default value"",,)")             | NULL
+ ("(cast,,,)")                          | ("(cast,,)")                          | NULL
+ ("(constraint,,,)")                    | ("(constraint,,)")                    | NULL
+ ("(conversion,,,)")                    | ("(conversion,,)")                    | NULL
+ ("(language,,,)")                      | ("(language,,)")                      | NULL
+ ("(""large object"",,,)")              | ("(""large object"",,)")              | NULL
+ ("(schema,,,)")                        | ("(schema,,)")                        | NULL
+ ("(""operator class"",,,)")            | ("(""operator class"",,)")            | NULL
+ ("(operator,,,)")                      | ("(operator,,)")                      | NULL
+ ("(rule,,,)")                          | ("(rule,,)")                          | NULL
+ ("(trigger,,,)")                       | ("(trigger,,)")                       | NULL
+ ("(""operator family"",,,)")           | ("(""operator family"",,)")           | NULL
+ ("(extension,,,)")                     | ("(extension,,)")                     | NULL
+ ("(policy,,,)")                        | ("(policy,,)")                        | NULL
+ ("(""statistics object"",,,)")         | ("(""statistics object"",,)")         | NULL
+ ("(collation,,,)")                     | ("(collation,,)")                     | NULL
+ ("(""event trigger"",,,)")             | ("(""event trigger"",,)")             | NULL
+ ("(transform,,,)")                     | ("(transform,,)")                     | NULL
+ ("(""text search dictionary"",,,)")    | ("(""text search dictionary"",,)")    | NULL
+ ("(""text search parser"",,,)")        | ("(""text search parser"",,)")        | NULL
+ ("(""text search configuration"",,,)") | ("(""text search configuration"",,)") | NULL
+ ("(""text search template"",,,)")      | ("(""text search template"",,)")      | NULL
+ ("(subscription,,,)")                  | ("(subscription,,)")                  | NULL
+ ("(publication,,,)")                   | ("(publication,,)")                   | NULL
+ ("(""publication relation"",,,)")      | ("(""publication relation"",,)")      | NULL
+(39 rows)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..cec44b2731 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -221,3 +221,59 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Keep this list in the same order as getObjectIdentityParts()
+WITH objects (classid, objid, objsubid) AS (VALUES
+    ('pg_class'::regclass, 0, 0), -- no relation
+    ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+    ('pg_proc'::regclass, 0, 0), -- no function
+    ('pg_type'::regclass, 0, 0), -- no type
+    ('pg_cast'::regclass, 0, 0), -- no cast
+    ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_constraint'::regclass, 0, 0), -- no constraint
+    ('pg_conversion'::regclass, 0, 0), -- no conversion
+    ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+    ('pg_language'::regclass, 0, 0), -- no language
+    ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+    ('pg_operator'::regclass, 0, 0), -- no operator
+    ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+    ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+    ('pg_am'::regclass, 0, 0), -- no access method
+    ('pg_amop'::regclass, 0, 0), -- no AM operator
+    ('pg_amproc'::regclass, 0, 0), -- no AM proc
+    ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+    ('pg_trigger'::regclass, 0, 0), -- no trigger
+    ('pg_namespace'::regclass, 0, 0), -- no schema
+    ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+    ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+    ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+    ('pg_ts_template'::regclass, 0, 0), -- no TS template
+    ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+    ('pg_authid'::regclass, 0, 0), -- no role
+    ('pg_database'::regclass, 0, 0), -- no database
+    ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+    ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+    ('pg_foreign_server'::regclass, 0, 0), -- no server
+    ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+    ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+    ('pg_extension'::regclass, 0, 0), -- no extension
+    ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+    ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_publication'::regclass, 0, 0), -- no publication
+    ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+    ('pg_subscription'::regclass, 0, 0), -- no subscription
+    ('pg_transform'::regclass, 0, 0) -- no transformation
+  )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+         AS ident,
+       ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+         AS addr,
+       pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+         AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
-- 
2.23.0

#53Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#52)
Re: Cache lookup errors with functions manipulation object addresses

On 17 Oct 2019, at 03:37, Michael Paquier <michael@paquier.xyz> wrote:

Attached is an updated
patch set with the heap_close() calls removed as per the recent report
from Dmitry.

Taking a look at this to see if we can achieve closure on this long-running
patchset. The goal of the patch is IMO a clear win for usability.

The patchset applies with a bit of fuzzing and offsetting, it has tests (which
pass) as well as the relevant documentation changes. I agree with the previous
reviewers that the new shape of the test is better, so definitely +1 on that
change. There are a lot of mechanic changes in this set, but AFAICT they cover
all the bases.

Since the patch has been through a lot of review already there isn't a lot to
say, only a few very minor things that stood out:

* This exports the useful functionality of regoperatorout for use
* in other backend modules. The result is a palloc'd string.
format_operator_extended has this comment, but the code can now also return
NULL and not just a palloc'd string.

+                   if (!missing_ok)
+                   {
+                       elog(ERROR, "could not find tuple for cast %u",
+                            object->objectId);
+                   }
The other (!missing_ok) blocks in this patch are not encapsulating the elog()
with braces.

I'm switching this to ready for committer,

cheers ./daniel

#54Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#53)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Mar 19, 2020 at 08:48:58AM +0100, Daniel Gustafsson wrote:

Taking a look at this to see if we can achieve closure on this long-running
patchset. The goal of the patch is IMO a clear win for usability.

The patchset applies with a bit of fuzzing and offsetting, it has tests (which
pass) as well as the relevant documentation changes. I agree with the previous
reviewers that the new shape of the test is better, so definitely +1 on that
change. There are a lot of mechanic changes in this set, but AFAICT they cover
all the bases.

Thanks. I hope it is helpful.

Since the patch has been through a lot of review already there isn't a lot to
say, only a few very minor things that stood out:

* This exports the useful functionality of regoperatorout for use
* in other backend modules. The result is a palloc'd string.
format_operator_extended has this comment, but the code can now also return
NULL and not just a palloc'd string.

+                   if (!missing_ok)
+                   {
+                       elog(ERROR, "could not find tuple for cast %u",
+                            object->objectId);
+                   }
The other (!missing_ok) blocks in this patch are not encapsulating the elog()
with braces.

I'm switching this to ready for committer,

The new FORMAT_TYPE_* flag still makes sense to me while reading this
code once again, as well as the extensibility of the new APIs for
operators and procedures. One doubt I still have is if we should
really change the signature of getObjectDescription() to include the
new missing_ok argument or if a new function should be introduced.
I'd rather keep only one function as, even if this is called in many
places in the backend, I cannot track down an extension using it, but
I won't go against Alvaro's will either if he thinks something
different as this is his original design and commit as of f8348ea3.
--
Michael

#55Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#54)
3 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Thu, Mar 19, 2020 at 10:30:11PM +0900, Michael Paquier wrote:

The new FORMAT_TYPE_* flag still makes sense to me while reading this
code once again, as well as the extensibility of the new APIs for
operators and procedures. One doubt I still have is if we should
really change the signature of getObjectDescription() to include the
new missing_ok argument or if a new function should be introduced.
I'd rather keep only one function as, even if this is called in many
places in the backend, I cannot track down an extension using it, but
I won't go against Alvaro's will either if he thinks something
different as this is his original design and commit as of f8348ea3.

Attached is an updated patch set, because of conflicts in the docs.
Daniel, you are still registered as a reviewer of this patch, and it
is marked as ready for committer? Do you have more comments? Would
anybody have objections if I move on with 0001 and 0002 that extend
the APIs to get the procedure, operator and type names?
--
Michael

Attachments:

v19-0003-Eliminate-user-visible-cache-lookup-errors-for-o.patchtext/x-diff; charset=us-asciiDownload
From 35fa5c7460954973cf2842d06b1059a070290c3b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 2 Jul 2020 14:12:39 +0900
Subject: [PATCH v19 3/3] Eliminate user-visible cache lookup errors for
 objaddr SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 src/include/catalog/objectaddress.h          |  12 +-
 src/include/utils/regproc.h                  |   5 +-
 src/backend/catalog/dependency.c             |  30 +-
 src/backend/catalog/objectaddress.c          | 998 ++++++++++++++-----
 src/backend/catalog/pg_depend.c              |   4 +-
 src/backend/catalog/pg_shdepend.c            |   8 +-
 src/backend/commands/event_trigger.c         |   9 +-
 src/backend/commands/extension.c             |   6 +-
 src/backend/commands/tablecmds.c             |  12 +-
 src/backend/utils/adt/regproc.c              |  20 +-
 src/test/regress/expected/object_address.out |  98 ++
 src/test/regress/sql/object_address.sql      |  56 ++
 doc/src/sgml/func.sgml                       |   7 +-
 contrib/sepgsql/database.c                   |   6 +-
 contrib/sepgsql/dml.c                        |   4 +-
 contrib/sepgsql/label.c                      |   4 +-
 contrib/sepgsql/proc.c                       |  14 +-
 contrib/sepgsql/relation.c                   |  20 +-
 contrib/sepgsql/schema.c                     |   6 +-
 19 files changed, 991 insertions(+), 328 deletions(-)

diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 144715d4f4..b876617c9d 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -70,14 +70,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 										   AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-									List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern struct ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index bb8a151b11..1d770911ef 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,11 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-								   List **objargs);
+								   List **objargs, bool missing_ok);
+
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-								  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b33a2f94af..f515e2c308 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -743,8 +743,8 @@ findDependentObjects(const ObjectAddress *object,
 				if (!object_address_present_add_flags(object, objflags,
 													  targetObjects))
 					elog(ERROR, "deletion of owning object %s failed to delete %s",
-						 getObjectDescription(&otherObject),
-						 getObjectDescription(object));
+						 getObjectDescription(&otherObject, false),
+						 getObjectDescription(object, false));
 
 				/* And we're done here. */
 				return;
@@ -790,11 +790,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -812,14 +812,14 @@ findDependentObjects(const ObjectAddress *object,
 		char	   *otherObjDesc;
 
 		if (OidIsValid(partitionObject.classId))
-			otherObjDesc = getObjectDescription(&partitionObject);
+			otherObjDesc = getObjectDescription(&partitionObject, false);
 		else
-			otherObjDesc = getObjectDescription(&owningObject);
+			otherObjDesc = getObjectDescription(&owningObject, false);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 				 errmsg("cannot drop %s because %s requires it",
-						getObjectDescription(object), otherObjDesc),
+						getObjectDescription(object, false), otherObjDesc),
 				 errhint("You can drop %s instead.", otherObjDesc)));
 	}
 
@@ -929,12 +929,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -1052,12 +1052,13 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			!(extra->flags & DEPFLAG_PARTITION))
 		{
 			const ObjectAddress *object = &targetObjects->refs[i];
-			char	   *otherObjDesc = getObjectDescription(&extra->dependee);
+			char	   *otherObjDesc = getObjectDescription(&extra->dependee,
+															false);
 
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because %s requires it",
-							getObjectDescription(object), otherObjDesc),
+							getObjectDescription(object, false), otherObjDesc),
 					 errhint("You can drop %s instead.", otherObjDesc)));
 		}
 	}
@@ -1105,7 +1106,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -1129,7 +1130,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1187,7 +1189,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 534df8e802..5e998fdd42 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -879,14 +879,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 											   bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-									   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1759,7 +1765,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1790,7 +1796,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2844,10 +2850,12 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string. In the event of an undefined object,
+ * NULL is returned if missing_ok is true, and an error is generated if
+ * missing_ok is false.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2857,14 +2865,19 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char *attname = get_attname(object->objectId,
+											object->objectSubId,
+											missing_ok);
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2876,14 +2889,28 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+													FORMAT_PROC_INVALID_AS_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				char *typname = format_type_extended(object->objectId, -1,
+													 FORMAT_TYPE_INVALID_AS_NULL);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2906,8 +2933,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					table_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2929,8 +2965,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2954,8 +2997,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2963,7 +3011,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2988,8 +3036,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3027,8 +3080,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					table_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -3038,7 +3098,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -3046,19 +3106,34 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char *langname = get_language_name(object->objectId,
+												   missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+													FORMAT_OPERATOR_INVALID_AS_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -3071,8 +3146,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -3099,7 +3179,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -3109,8 +3189,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3140,13 +3225,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3190,13 +3282,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3239,12 +3338,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3277,12 +3384,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					table_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3299,8 +3414,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3314,8 +3433,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3341,8 +3465,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3367,8 +3495,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3393,8 +3526,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3419,8 +3557,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3438,8 +3581,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	  *username = GetUserNameFromId(object->objectId,
+														missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3449,8 +3595,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3461,8 +3611,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3471,8 +3625,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3480,8 +3636,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3496,8 +3653,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3537,8 +3699,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3621,8 +3790,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3634,8 +3807,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3664,12 +3841,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					table_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3682,9 +3867,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char *pubname = get_publication_name(object->objectId,
+													 missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3698,14 +3885,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3717,9 +3908,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char *subname = get_subscription_name(object->objectId,
+													  missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3731,8 +3924,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3750,6 +3947,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3765,7 +3966,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3774,7 +3975,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3784,7 +3985,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3845,7 +4050,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3855,7 +4060,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3899,7 +4108,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3914,6 +4127,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3988,12 +4202,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * cannot be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -4004,7 +4224,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -4013,8 +4233,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -4058,26 +4283,34 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	if (names != NIL)
-		values[1] = PointerGetDatum(strlist_to_textarray(names));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[1] = false;
+	{
+		pfree(identity);
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_names */
+		if (names != NIL)
+			values[1] = PointerGetDatum(strlist_to_textarray(names));
+		else
+			values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -4091,7 +4324,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4101,11 +4334,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -4121,7 +4356,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4258,6 +4494,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4265,7 +4505,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4273,7 +4514,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4320,7 +4568,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4330,7 +4578,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		table_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4348,7 +4605,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4356,7 +4614,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "routine" for an undefined object */
+		appendStringInfoString(buffer, "routine");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4373,12 +4638,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate. Returns NULL if the object defined
+ * cannot be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4387,11 +4653,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress. Returns NULL if
+ * the object defined cannot be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4413,31 +4681,63 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing
+				 * we can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1,
+					FORMAT_TYPE_INVALID_AS_NULL | FORMAT_TYPE_FORCE_QUALIFY);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4456,8 +4756,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					table_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4484,8 +4790,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4506,15 +4816,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4529,7 +4844,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4548,8 +4864,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4585,8 +4905,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					table_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4596,7 +4923,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4611,8 +4939,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4622,6 +4954,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4629,11 +4963,18 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				char *oprname = format_operator_extended(object->objectId,
+						FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_INVALID_AS_NULL);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4646,8 +4987,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4673,7 +5018,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4682,8 +5028,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4715,13 +5065,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4769,13 +5127,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4810,14 +5176,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					table_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4837,14 +5209,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					table_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4858,8 +5236,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4876,8 +5258,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4899,8 +5285,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4922,8 +5312,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4945,8 +5339,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4968,8 +5366,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4986,7 +5388,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -5000,8 +5404,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -5015,8 +5423,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -5028,10 +5440,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -5039,11 +5455,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -5058,8 +5478,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -5106,8 +5530,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5169,8 +5601,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5189,8 +5625,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5210,14 +5650,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					table_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5229,11 +5675,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5246,13 +5695,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5266,11 +5719,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5289,8 +5745,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					table_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5316,20 +5778,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty string is equivalent to no object defined */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5339,7 +5815,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5368,7 +5848,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5377,7 +5858,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 21cfdcace9..70baf03178 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -536,7 +536,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index f776e821b3..082b935a69 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -638,7 +638,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1147,7 +1147,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1283,7 +1283,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1429,7 +1429,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 151cdfff39..7844880170 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1267,10 +1267,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -1929,8 +1930,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 3b69ab7ed5..c796fcd8da 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2919,7 +2919,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3328,7 +3328,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3368,7 +3368,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..d651f7cb1d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11359,7 +11359,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -11375,7 +11375,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -11394,7 +11394,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -11412,7 +11412,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -11473,7 +11473,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
@@ -11529,7 +11529,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			  foundDep->refobjsubid != 0)
 			)
 			elog(ERROR, "found unexpected dependency for column: %s",
-				 getObjectDescription(&foundObject));
+				 getObjectDescription(&foundObject, false));
 
 		CatalogTupleDelete(depRel, &depTup->t_self);
 	}
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index f9352ccca7..39e49d048b 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -413,7 +413,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -423,7 +424,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -853,15 +858,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..de43fd5381 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -521,3 +521,101 @@ drop cascades to function trig()
 drop cascades to function proc(integer)
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Keep this list in the same order as getObjectIdentityParts()
+WITH objects (classid, objid, objsubid) AS (VALUES
+    ('pg_class'::regclass, 0, 0), -- no relation
+    ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+    ('pg_proc'::regclass, 0, 0), -- no function
+    ('pg_type'::regclass, 0, 0), -- no type
+    ('pg_cast'::regclass, 0, 0), -- no cast
+    ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_constraint'::regclass, 0, 0), -- no constraint
+    ('pg_conversion'::regclass, 0, 0), -- no conversion
+    ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+    ('pg_language'::regclass, 0, 0), -- no language
+    ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+    ('pg_operator'::regclass, 0, 0), -- no operator
+    ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+    ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+    ('pg_am'::regclass, 0, 0), -- no access method
+    ('pg_amop'::regclass, 0, 0), -- no AM operator
+    ('pg_amproc'::regclass, 0, 0), -- no AM proc
+    ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+    ('pg_trigger'::regclass, 0, 0), -- no trigger
+    ('pg_namespace'::regclass, 0, 0), -- no schema
+    ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+    ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+    ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+    ('pg_ts_template'::regclass, 0, 0), -- no TS template
+    ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+    ('pg_authid'::regclass, 0, 0), -- no role
+    ('pg_database'::regclass, 0, 0), -- no database
+    ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+    ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+    ('pg_foreign_server'::regclass, 0, 0), -- no server
+    ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+    ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+    ('pg_extension'::regclass, 0, 0), -- no extension
+    ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+    ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_publication'::regclass, 0, 0), -- no publication
+    ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+    ('pg_subscription'::regclass, 0, 0), -- no subscription
+    ('pg_transform'::regclass, 0, 0) -- no transformation
+  )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+         AS ident,
+       ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+         AS addr,
+       pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+         AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
+                 ident                  |                 addr                  | descr 
+----------------------------------------+---------------------------------------+-------
+ ("(""default acl"",,,)")               | ("(""default acl"",,)")               | NULL
+ ("(tablespace,,,)")                    | ("(tablespace,,)")                    | NULL
+ ("(type,,,)")                          | ("(type,,)")                          | NULL
+ ("(routine,,,)")                       | ("(routine,,)")                       | NULL
+ ("(relation,,,)")                      | ("(relation,,)")                      | NULL
+ ("(""table column"",,,)")              | ("(""table column"",,)")              | NULL
+ ("(role,,,)")                          | ("(role,,)")                          | NULL
+ ("(database,,,)")                      | ("(database,,)")                      | NULL
+ ("(server,,,)")                        | ("(server,,)")                        | NULL
+ ("(""user mapping"",,,)")              | ("(""user mapping"",,)")              | NULL
+ ("(""foreign-data wrapper"",,,)")      | ("(""foreign-data wrapper"",,)")      | NULL
+ ("(""access method"",,,)")             | ("(""access method"",,)")             | NULL
+ ("(""operator of access method"",,,)") | ("(""operator of access method"",,)") | NULL
+ ("(""function of access method"",,,)") | ("(""function of access method"",,)") | NULL
+ ("(""default value"",,,)")             | ("(""default value"",,)")             | NULL
+ ("(cast,,,)")                          | ("(cast,,)")                          | NULL
+ ("(constraint,,,)")                    | ("(constraint,,)")                    | NULL
+ ("(conversion,,,)")                    | ("(conversion,,)")                    | NULL
+ ("(language,,,)")                      | ("(language,,)")                      | NULL
+ ("(""large object"",,,)")              | ("(""large object"",,)")              | NULL
+ ("(schema,,,)")                        | ("(schema,,)")                        | NULL
+ ("(""operator class"",,,)")            | ("(""operator class"",,)")            | NULL
+ ("(operator,,,)")                      | ("(operator,,)")                      | NULL
+ ("(rule,,,)")                          | ("(rule,,)")                          | NULL
+ ("(trigger,,,)")                       | ("(trigger,,)")                       | NULL
+ ("(""operator family"",,,)")           | ("(""operator family"",,)")           | NULL
+ ("(extension,,,)")                     | ("(extension,,)")                     | NULL
+ ("(policy,,,)")                        | ("(policy,,)")                        | NULL
+ ("(""statistics object"",,,)")         | ("(""statistics object"",,)")         | NULL
+ ("(collation,,,)")                     | ("(collation,,)")                     | NULL
+ ("(""event trigger"",,,)")             | ("(""event trigger"",,)")             | NULL
+ ("(transform,,,)")                     | ("(transform,,)")                     | NULL
+ ("(""text search dictionary"",,,)")    | ("(""text search dictionary"",,)")    | NULL
+ ("(""text search parser"",,,)")        | ("(""text search parser"",,)")        | NULL
+ ("(""text search configuration"",,,)") | ("(""text search configuration"",,)") | NULL
+ ("(""text search template"",,,)")      | ("(""text search template"",,)")      | NULL
+ ("(subscription,,,)")                  | ("(subscription,,)")                  | NULL
+ ("(publication,,,)")                   | ("(publication,,)")                   | NULL
+ ("(""publication relation"",,,)")      | ("(""publication relation"",,)")      | NULL
+(39 rows)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..cec44b2731 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -221,3 +221,59 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Keep this list in the same order as getObjectIdentityParts()
+WITH objects (classid, objid, objsubid) AS (VALUES
+    ('pg_class'::regclass, 0, 0), -- no relation
+    ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+    ('pg_proc'::regclass, 0, 0), -- no function
+    ('pg_type'::regclass, 0, 0), -- no type
+    ('pg_cast'::regclass, 0, 0), -- no cast
+    ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_constraint'::regclass, 0, 0), -- no constraint
+    ('pg_conversion'::regclass, 0, 0), -- no conversion
+    ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+    ('pg_language'::regclass, 0, 0), -- no language
+    ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+    ('pg_operator'::regclass, 0, 0), -- no operator
+    ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+    ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+    ('pg_am'::regclass, 0, 0), -- no access method
+    ('pg_amop'::regclass, 0, 0), -- no AM operator
+    ('pg_amproc'::regclass, 0, 0), -- no AM proc
+    ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+    ('pg_trigger'::regclass, 0, 0), -- no trigger
+    ('pg_namespace'::regclass, 0, 0), -- no schema
+    ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+    ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+    ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+    ('pg_ts_template'::regclass, 0, 0), -- no TS template
+    ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+    ('pg_authid'::regclass, 0, 0), -- no role
+    ('pg_database'::regclass, 0, 0), -- no database
+    ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+    ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+    ('pg_foreign_server'::regclass, 0, 0), -- no server
+    ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+    ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+    ('pg_extension'::regclass, 0, 0), -- no extension
+    ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+    ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_publication'::regclass, 0, 0), -- no publication
+    ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+    ('pg_subscription'::regclass, 0, 0), -- no subscription
+    ('pg_transform'::regclass, 0, 0) -- no transformation
+  )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+         AS ident,
+       ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+         AS addr,
+       pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+         AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f065856535..2d72a3390c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22826,7 +22826,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
         object).  This description is intended to be human-readable, and might
         be translated, depending on server configuration.  This is especially
         useful to determine the identity of an object referenced in the
-        <structname>pg_depend</structname> catalog.
+        <structname>pg_depend</structname> catalog. This function returns
+        <literal>NULL</literal> values for undefined objects.
        </para></entry>
       </row>
 
@@ -22858,7 +22859,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
         otherwise <literal>NULL</literal>;
         <parameter>identity</parameter> is the complete object identity, with
         the precise format depending on object type, and each name within the
-        format being schema-qualified and quoted as necessary.
+        format being schema-qualified and quoted as necessary. Undefined
+        objects are identified with <literal>NULL</literal> values.
        </para></entry>
       </row>
 
@@ -22915,6 +22917,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
         <parameter>objsubid</parameter> is the sub-object ID, or zero if none.
         This function is the inverse
         of <function>pg_identify_object_as_address</function>.
+        Undefined objects are identified with <literal>NULL</literal> values.
        </para></entry>
       </row>
      </tbody>
diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 4e83b7bfa8..ec20378593 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 53f6f41c5c..75ee612bcd 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -179,7 +179,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -256,7 +256,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 147ab67f32..32e405530b 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -355,7 +355,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -523,7 +523,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 2c244a9003..d5d7dbe103 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -80,7 +80,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -114,7 +114,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -164,7 +164,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -179,7 +179,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -204,7 +204,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -292,7 +292,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -324,7 +324,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 380bc6094d..b50f386f5b 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -102,7 +102,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -146,7 +146,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -178,7 +178,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -222,7 +222,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -288,7 +288,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -450,7 +450,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -472,7 +472,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -503,7 +503,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -584,7 +584,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -695,7 +695,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 90ecbc1725..3b2b80be83 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -123,7 +123,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -148,7 +148,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -186,7 +186,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
-- 
2.27.0

v19-0001-Add-flag-to-format_type_extended-to-enforce-NULL.patchtext/x-diff; charset=us-asciiDownload
From f0c8ffafcbcd0a43ff9729321cd68dd592a54c5c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 17 Oct 2019 10:25:24 +0900
Subject: [PATCH v19 1/3] Add flag to format_type_extended to enforce NULL-ness

If a type scanned is undefined, type format routines have two behaviors
depending on if FORMAT_TYPE_ALLOW_INVALID is defined by the caller:
- Generate an error
- Return undefined type name "???" or "-".

The current interface is unhelpful for callers willing to format
properly a type name, but still make sure that the type is defined as
there could be types matching the undefined strings.  In order to
counter that, add a new flag called FORMAT_TYPE_INVALID_AS_NULL which
returns a NULL result instead of "??? or "-" which does not generate an
error. They will be used for future patches to improve the SQL interface
for object addresses.
---
 src/include/utils/builtins.h        |  1 +
 src/backend/utils/adt/format_type.c | 22 +++++++++++++++++-----
 2 files changed, 18 insertions(+), 5 deletions(-)

diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index f8595642da..3ca5e938f8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -113,6 +113,7 @@ extern Datum numeric_float8_no_overflow(PG_FUNCTION_ARGS);
 #define FORMAT_TYPE_TYPEMOD_GIVEN	0x01	/* typemod defined by caller */
 #define FORMAT_TYPE_ALLOW_INVALID	0x02	/* allow invalid types */
 #define FORMAT_TYPE_FORCE_QUALIFY	0x04	/* force qualification of type */
+#define FORMAT_TYPE_INVALID_AS_NULL	0x08	/* NULL if undefined */
 extern char *format_type_extended(Oid type_oid, int32 typemod, bits16 flags);
 
 extern char *format_type_be(Oid type_oid);
diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c
index df0bdeb610..f2816e4f37 100644
--- a/src/backend/utils/adt/format_type.c
+++ b/src/backend/utils/adt/format_type.c
@@ -96,13 +96,16 @@ format_type(PG_FUNCTION_ARGS)
  * - FORMAT_TYPE_ALLOW_INVALID
  *			if the type OID is invalid or unknown, return ??? or such instead
  *			of failing
+ * - FORMAT_TYPE_INVALID_AS_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of ???
+ *			or such
  * - FORMAT_TYPE_FORCE_QUALIFY
  *			always schema-qualify type names, regardless of search_path
  *
  * Note that TYPEMOD_GIVEN is not interchangeable with "typemod == -1";
  * see the comments above for format_type().
  *
- * Returns a palloc'd string.
+ * Returns a palloc'd string, or NULL.
  */
 char *
 format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
@@ -114,13 +117,20 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 	char	   *buf;
 	bool		with_typemod;
 
-	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
-		return pstrdup("-");
+	if (type_oid == InvalidOid)
+	{
+		if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			return pstrdup("-");
+	}
 
 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
 	if (!HeapTupleIsValid(tuple))
 	{
-		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+		if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
+			return NULL;
+		else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 			return pstrdup("???");
 		else
 			elog(ERROR, "cache lookup failed for type %u", type_oid);
@@ -144,7 +154,9 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
 		if (!HeapTupleIsValid(tuple))
 		{
-			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
+			if ((flags & FORMAT_TYPE_INVALID_AS_NULL) != 0)
+				return NULL;
+			else if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
 				return pstrdup("???[]");
 			else
 				elog(ERROR, "cache lookup failed for type %u", type_oid);
-- 
2.27.0

v19-0002-Refactor-format-procedure-and-operator-APIs-to-b.patchtext/x-diff; charset=us-asciiDownload
From fd33019e5e0c41aa8b64e7ed429fcf468695ce99 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 17 Oct 2019 10:25:39 +0900
Subject: [PATCH v19 2/3] Refactor format procedure and operator APIs to be
 more modular

This introduce a new set of extended routines for procedure and operator
lookups, with a flags bitmask argument that can modify the default
behavior:
- Force schema qualification
- Force NULL as result instead of a numeric OID for an undefined
object.
---
 src/include/utils/regproc.h     | 10 ++++++
 src/backend/utils/adt/regproc.c | 61 +++++++++++++++++++++++----------
 2 files changed, 52 insertions(+), 19 deletions(-)

diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 383dfe641e..bb8a151b11 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -15,6 +15,16 @@
 
 #include "nodes/pg_list.h"
 
+/* Control flags for format_procedure_extended */
+#define FORMAT_PROC_INVALID_AS_NULL	0x01	/* NULL if undefined */
+#define FORMAT_PROC_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_procedure_extended(Oid type_oid, bits16 flags);
+
+/* Control flags for format_operator_extended */
+#define FORMAT_OPERATOR_INVALID_AS_NULL	0x01	/* NULL if undefined */
+#define FORMAT_OPERATOR_FORCE_QUALIFY	0x02	/* force qualification */
+extern char *format_operator_extended(Oid type_oid, bits16 flags);
+
 extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index c800d797ac..f9352ccca7 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -41,8 +41,6 @@
 #include "utils/syscache.h"
 #include "utils/varlena.h"
 
-static char *format_operator_internal(Oid operator_oid, bool force_qualify);
-static char *format_procedure_internal(Oid procedure_oid, bool force_qualify);
 static void parseNameAndArgTypes(const char *string, bool allowNone,
 								 List **names, int *nargs, Oid *argtypes);
 
@@ -323,24 +321,27 @@ to_regprocedure(PG_FUNCTION_ARGS)
 char *
 format_procedure(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, false);
+	return format_procedure_extended(procedure_oid, 0);
 }
 
 char *
 format_procedure_qualified(Oid procedure_oid)
 {
-	return format_procedure_internal(procedure_oid, true);
+	return format_procedure_extended(procedure_oid, FORMAT_PROC_FORCE_QUALIFY);
 }
 
 /*
  * Routine to produce regprocedure names; see format_procedure above.
  *
- * force_qualify says whether to schema-qualify; if true, the name is always
- * qualified regardless of search_path visibility.  Otherwise the name is only
- * qualified if the function is not in path.
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_PROC_INVALID_AS_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_PROC_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_procedure_internal(Oid procedure_oid, bool force_qualify)
+char *
+format_procedure_extended(Oid procedure_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	proctup;
@@ -365,7 +366,8 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		 * Would this proc be found (given the right args) by regprocedurein?
 		 * If not, or if caller requests it, we need to qualify it.
 		 */
-		if (!force_qualify && FunctionIsVisible(procedure_oid))
+		if ((flags & FORMAT_PROC_FORCE_QUALIFY) == 0 &&
+			FunctionIsVisible(procedure_oid))
 			nspname = NULL;
 		else
 			nspname = get_namespace_name(procform->pronamespace);
@@ -379,7 +381,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 			if (i > 0)
 				appendStringInfoChar(&buf, ',');
 			appendStringInfoString(&buf,
-								   force_qualify ?
+								   (flags & FORMAT_PROC_FORCE_QUALIFY) != 0 ?
 								   format_type_be_qualified(thisargtype) :
 								   format_type_be(thisargtype));
 		}
@@ -389,6 +391,11 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 
 		ReleaseSysCache(proctup);
 	}
+	else if ((flags & FORMAT_PROC_INVALID_AS_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/* If OID doesn't match any pg_proc entry, return it numerically */
@@ -747,13 +754,22 @@ to_regoperator(PG_FUNCTION_ARGS)
 }
 
 /*
- * format_operator		- converts operator OID to "opr_name(args)"
+ * format_operator_extended
+ *
+ * Converts operator OID to "opr_name(args)"
  *
  * This exports the useful functionality of regoperatorout for use
  * in other backend modules.  The result is a palloc'd string.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_OPERATOR_INVALID_AS_NULL
+ *			if the type OID is invalid or unknown, return NULL instead of
+ *			the numeric OID.
+ * - FORMAT_OPERATOR_FORCE_QUALIFY
+ *			always schema-qualify type names, regardless of search_path
  */
-static char *
-format_operator_internal(Oid operator_oid, bool force_qualify)
+char *
+format_operator_extended(Oid operator_oid, bits16 flags)
 {
 	char	   *result;
 	HeapTuple	opertup;
@@ -776,7 +792,8 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		 * Would this oper be found (given the right args) by regoperatorin?
 		 * If not, or if caller explicitly requests it, we need to qualify it.
 		 */
-		if (force_qualify || !OperatorIsVisible(operator_oid))
+		if ((flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ||
+			!OperatorIsVisible(operator_oid))
 		{
 			nspname = get_namespace_name(operform->oprnamespace);
 			appendStringInfo(&buf, "%s.",
@@ -787,7 +804,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprleft)
 			appendStringInfo(&buf, "%s,",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprleft) :
 							 format_type_be(operform->oprleft));
 		else
@@ -795,7 +812,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		if (operform->oprright)
 			appendStringInfo(&buf, "%s)",
-							 force_qualify ?
+							 (flags & FORMAT_OPERATOR_FORCE_QUALIFY) != 0 ?
 							 format_type_be_qualified(operform->oprright) :
 							 format_type_be(operform->oprright));
 		else
@@ -805,6 +822,11 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 
 		ReleaseSysCache(opertup);
 	}
+	else if ((flags & FORMAT_OPERATOR_INVALID_AS_NULL) != 0)
+	{
+		/* If object is undefined, return NULL as wanted by caller */
+		result = NULL;
+	}
 	else
 	{
 		/*
@@ -820,13 +842,14 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 char *
 format_operator(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, false);
+	return format_operator_extended(operator_oid, 0);
 }
 
 char *
 format_operator_qualified(Oid operator_oid)
 {
-	return format_operator_internal(operator_oid, true);
+	return format_operator_extended(operator_oid,
+									FORMAT_OPERATOR_FORCE_QUALIFY);
 }
 
 void
-- 
2.27.0

#56Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#55)
Re: Cache lookup errors with functions manipulation object addresses

On 2 Jul 2020, at 07:35, Michael Paquier <michael@paquier.xyz> wrote:

On Thu, Mar 19, 2020 at 10:30:11PM +0900, Michael Paquier wrote:

The new FORMAT_TYPE_* flag still makes sense to me while reading this
code once again, as well as the extensibility of the new APIs for
operators and procedures. One doubt I still have is if we should
really change the signature of getObjectDescription() to include the
new missing_ok argument or if a new function should be introduced.
I'd rather keep only one function as, even if this is called in many
places in the backend, I cannot track down an extension using it, but
I won't go against Alvaro's will either if he thinks something
different as this is his original design and commit as of f8348ea3.

Attached is an updated patch set, because of conflicts in the docs.
Daniel, you are still registered as a reviewer of this patch, and it
is marked as ready for committer? Do you have more comments? Would
anybody have objections if I move on with 0001 and 0002 that extend
the APIs to get the procedure, operator and type names?

No objections from me after skimming over the updated patchset. I still
maintain that the format_operator_extended comment should be amended to include
that it can return NULL and not alwatys palloced string, but that's it.

cheers ./daniel

#57Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#55)
Re: Cache lookup errors with functions manipulation object addresses

On 2020-Jul-02, Michael Paquier wrote:

Attached is an updated patch set, because of conflicts in the docs.
Daniel, you are still registered as a reviewer of this patch, and it
is marked as ready for committer? Do you have more comments? Would
anybody have objections if I move on with 0001 and 0002 that extend
the APIs to get the procedure, operator and type names?

0001 and 0002 look good to me.

I think 0003 could be a little more careful about indentation; some long
lines are going to result after pgindent that might be better to handle
in a different way before commit, e.g., here

+			{
+				char *proname = format_procedure_extended(object->objectId,
+						FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL);

I don't have any substantive comments.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#58Michael Paquier
michael@paquier.xyz
In reply to: Daniel Gustafsson (#56)
Re: Cache lookup errors with functions manipulation object addresses

On Fri, Jul 03, 2020 at 12:34:39PM +0200, Daniel Gustafsson wrote:

No objections from me after skimming over the updated patchset. I still
maintain that the format_operator_extended comment should be amended to include
that it can return NULL and not alwatys palloced string, but that's it.

Okay, 0001 and 0002 are now committed. The same comment applies to
the procedure part, and I have noticed three more inconsistencies in
0002, the comments of format_procedure_extended had better match its
operator sibling, the new declarations in regproc.h still used
type_oid for the first argument, and the description of the flags
still referred to a type name for both routines. I am looking at
0003 with fresh eyes now, and I'll try to send a rebased version
shortly.
--
Michael

#59Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#57)
1 attachment(s)
Re: Cache lookup errors with functions manipulation object addresses

On Fri, Jul 03, 2020 at 11:04:17AM -0400, Alvaro Herrera wrote:

0001 and 0002 look good to me.

Thanks for the review.

I think 0003 could be a little more careful about indentation; some long
lines are going to result after pgindent that might be better to handle
in a different way before commit, e.g., here

+    {
+        char *proname = format_procedure_extended(object->objectId,
+            FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL);

Yes, I was looking at that for a couple of hours and pgindent made
that a bit weird. So I have changed the code to just use a separate
variable. That makes things a bit cleaner.

While refreshing my mind with this code, I got surprised with the
choice of "routine" in getProcedureTypeDescription() when we need a
default object type name for an object not found, so I have switched
that to "procedure" to be more consistent.

I have also spent some time analyzing the coverage of the patch, and
did not find any obvious holes or any remaining missing_ok paths not
covered. Some comments were also a bit weird after re-reading them,
so I tweaked a couple of places.

Attached is for now a rebased patch. If there are any comments,
please feel free. Daniel, Alvaro, does that look fine for you? I am
letting this stuff aside for a couple of days for the moment.
--
Michael

Attachments:

v20-0001-Eliminate-user-visible-cache-lookup-errors-for-o.patchtext/x-diff; charset=us-asciiDownload
From 3ca2841d38880eb0f40427e9aa447b5ae3a2e66c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 6 Jul 2020 16:44:31 +0900
Subject: [PATCH v20] Eliminate user-visible cache lookup errors for objaddr
 SQL functions

When using the following functions, users could see various types of
errors like "cache lookup failed for OID XXX":
* pg_describe_object
* pg_identify_object_as_address
* pg_identify_object
All the lower set of APIs managing object addresses for all types of
the system are made smarter by gaining a missing_ok argument that allows
any caller to control if he is willing to have an actual error or if
he wants to control error at its level by having empty objects if they
are undefined.

Regression tests added in this commit failed with such errors before
being patched.
---
 src/include/catalog/objectaddress.h          |   12 +-
 src/include/utils/regproc.h                  |    5 +-
 src/backend/catalog/dependency.c             |   30 +-
 src/backend/catalog/objectaddress.c          | 1003 +++++++++++++-----
 src/backend/catalog/pg_depend.c              |    4 +-
 src/backend/catalog/pg_shdepend.c            |    8 +-
 src/backend/commands/event_trigger.c         |    9 +-
 src/backend/commands/extension.c             |    6 +-
 src/backend/commands/tablecmds.c             |   12 +-
 src/backend/utils/adt/regproc.c              |   20 +-
 src/test/regress/expected/object_address.out |   98 ++
 src/test/regress/sql/object_address.sql      |   56 +
 doc/src/sgml/func.sgml                       |    7 +-
 contrib/sepgsql/database.c                   |    6 +-
 contrib/sepgsql/dml.c                        |    4 +-
 contrib/sepgsql/label.c                      |    4 +-
 contrib/sepgsql/proc.c                       |   14 +-
 contrib/sepgsql/relation.c                   |   20 +-
 contrib/sepgsql/schema.c                     |    6 +-
 19 files changed, 996 insertions(+), 328 deletions(-)

diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 144715d4f4..b876617c9d 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -70,14 +70,18 @@ extern bool get_object_namensp_unique(Oid class_id);
 extern HeapTuple get_catalog_object_by_oid(Relation catalog,
 										   AttrNumber oidcol, Oid objectId);
 
-extern char *getObjectDescription(const ObjectAddress *object);
+extern char *getObjectDescription(const ObjectAddress *object,
+								  bool missing_ok);
 extern char *getObjectDescriptionOids(Oid classid, Oid objid);
 
 extern int	read_objtype_from_string(const char *objtype);
-extern char *getObjectTypeDescription(const ObjectAddress *object);
-extern char *getObjectIdentity(const ObjectAddress *address);
+extern char *getObjectTypeDescription(const ObjectAddress *object,
+									  bool missing_ok);
+extern char *getObjectIdentity(const ObjectAddress *address,
+							   bool missing_ok);
 extern char *getObjectIdentityParts(const ObjectAddress *address,
-									List **objname, List **objargs);
+									List **objname, List **objargs,
+									bool missing_ok);
 extern struct ArrayType *strlist_to_textarray(List *list);
 
 extern ObjectType get_relkind_objtype(char relkind);
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 145452a5ad..330417e888 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -29,10 +29,11 @@ extern List *stringToQualifiedNameList(const char *string);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid operator_oid, List **objnames,
-								   List **objargs);
+								   List **objargs, bool missing_ok);
+
 extern char *format_operator(Oid operator_oid);
 extern char *format_operator_qualified(Oid operator_oid);
 extern void format_operator_parts(Oid operator_oid, List **objnames,
-								  List **objargs);
+								  List **objargs, bool missing_ok);
 
 #endif
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index b33a2f94af..f515e2c308 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -743,8 +743,8 @@ findDependentObjects(const ObjectAddress *object,
 				if (!object_address_present_add_flags(object, objflags,
 													  targetObjects))
 					elog(ERROR, "deletion of owning object %s failed to delete %s",
-						 getObjectDescription(&otherObject),
-						 getObjectDescription(object));
+						 getObjectDescription(&otherObject, false),
+						 getObjectDescription(object, false));
 
 				/* And we're done here. */
 				return;
@@ -790,11 +790,11 @@ findDependentObjects(const ObjectAddress *object,
 				 * the depender fields...
 				 */
 				elog(ERROR, "incorrect use of PIN dependency with %s",
-					 getObjectDescription(object));
+					 getObjectDescription(object, false));
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				break;
 		}
 	}
@@ -812,14 +812,14 @@ findDependentObjects(const ObjectAddress *object,
 		char	   *otherObjDesc;
 
 		if (OidIsValid(partitionObject.classId))
-			otherObjDesc = getObjectDescription(&partitionObject);
+			otherObjDesc = getObjectDescription(&partitionObject, false);
 		else
-			otherObjDesc = getObjectDescription(&owningObject);
+			otherObjDesc = getObjectDescription(&owningObject, false);
 
 		ereport(ERROR,
 				(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 				 errmsg("cannot drop %s because %s requires it",
-						getObjectDescription(object), otherObjDesc),
+						getObjectDescription(object, false), otherObjDesc),
 				 errhint("You can drop %s instead.", otherObjDesc)));
 	}
 
@@ -929,12 +929,12 @@ findDependentObjects(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 						 errmsg("cannot drop %s because it is required by the database system",
-								getObjectDescription(object))));
+								getObjectDescription(object, false))));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 			default:
 				elog(ERROR, "unrecognized dependency type '%c' for %s",
-					 foundDep->deptype, getObjectDescription(object));
+					 foundDep->deptype, getObjectDescription(object, false));
 				subflags = 0;	/* keep compiler quiet */
 				break;
 		}
@@ -1052,12 +1052,13 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			!(extra->flags & DEPFLAG_PARTITION))
 		{
 			const ObjectAddress *object = &targetObjects->refs[i];
-			char	   *otherObjDesc = getObjectDescription(&extra->dependee);
+			char	   *otherObjDesc = getObjectDescription(&extra->dependee,
+															false);
 
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because %s requires it",
-							getObjectDescription(object), otherObjDesc),
+							getObjectDescription(object, false), otherObjDesc),
 					 errhint("You can drop %s instead.", otherObjDesc)));
 		}
 	}
@@ -1105,7 +1106,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		if (extra->flags & DEPFLAG_SUBOBJECT)
 			continue;
 
-		objDesc = getObjectDescription(obj);
+		objDesc = getObjectDescription(obj, false);
 
 		/*
 		 * If, at any stage of the recursive search, we reached the object via
@@ -1129,7 +1130,8 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 		}
 		else if (behavior == DROP_RESTRICT)
 		{
-			char	   *otherDesc = getObjectDescription(&extra->dependee);
+			char	   *otherDesc = getObjectDescription(&extra->dependee,
+														 false);
 
 			if (numReportedClient < MAX_REPORTED_DEPS)
 			{
@@ -1187,7 +1189,7 @@ reportDependentObjects(const ObjectAddresses *targetObjects,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because other objects depend on it",
-							getObjectDescription(origObject)),
+							getObjectDescription(origObject, false)),
 					 errdetail("%s", clientdetail.data),
 					 errdetail_log("%s", logdetail.data),
 					 errhint("Use DROP ... CASCADE to drop the dependent objects too.")));
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 534df8e802..0291cda7fb 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -879,14 +879,20 @@ static ObjectAddress get_object_address_defacl(List *object,
 											   bool missing_ok);
 static const ObjectPropertyType *get_object_property_data(Oid class_id);
 
-static void getRelationDescription(StringInfo buffer, Oid relid);
-static void getOpFamilyDescription(StringInfo buffer, Oid opfid);
+static void getRelationDescription(StringInfo buffer, Oid relid,
+								   bool missing_ok);
+static void getOpFamilyDescription(StringInfo buffer, Oid opfid,
+								   bool missing_ok);
 static void getRelationTypeDescription(StringInfo buffer, Oid relid,
-									   int32 objectSubId);
-static void getProcedureTypeDescription(StringInfo buffer, Oid procid);
-static void getConstraintTypeDescription(StringInfo buffer, Oid constroid);
-static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object);
-static void getRelationIdentity(StringInfo buffer, Oid relid, List **object);
+									   int32 objectSubId, bool missing_ok);
+static void getProcedureTypeDescription(StringInfo buffer, Oid procid,
+										bool missing_ok);
+static void getConstraintTypeDescription(StringInfo buffer, Oid constroid,
+										 bool missing_ok);
+static void getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+								bool missing_ok);
+static void getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+								bool missing_ok);
 
 /*
  * Translate an object name and arguments (as passed by the parser) to an
@@ -1759,7 +1765,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -1790,7 +1796,7 @@ get_object_address_opf_member(ObjectType objtype,
 										membernum,
 										TypeNameToString(typenames[0]),
 										TypeNameToString(typenames[1]),
-										getObjectDescription(&famaddr))));
+										getObjectDescription(&famaddr, false))));
 				}
 				else
 				{
@@ -2844,10 +2850,11 @@ get_catalog_object_by_oid(Relation catalog, AttrNumber oidcol, Oid objectId)
 /*
  * getObjectDescription: build an object description for messages
  *
- * The result is a palloc'd string.
+ * The result is a palloc'd string.  NULL is returned for an undefined
+ * object if missing_ok is true, else an error is generated.
  */
 char *
-getObjectDescription(const ObjectAddress *object)
+getObjectDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -2857,14 +2864,20 @@ getObjectDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			if (object->objectSubId == 0)
-				getRelationDescription(&buffer, object->objectId);
+				getRelationDescription(&buffer, object->objectId, missing_ok);
 			else
 			{
 				/* column, not whole relation */
 				StringInfoData rel;
+				char	   *attname = get_attname(object->objectId,
+												  object->objectSubId,
+												  missing_ok);
+
+				if (!attname)
+					break;
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, object->objectId);
+				getRelationDescription(&rel, object->objectId, missing_ok);
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("column %s of %s"),
 								 get_attname(object->objectId,
@@ -2876,14 +2889,30 @@ getObjectDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfo(&buffer, _("function %s"),
-							 format_procedure(object->objectId));
-			break;
+			{
+				bits16		flags = FORMAT_PROC_INVALID_AS_NULL;
+				char	   *proname = format_procedure_extended(object->objectId,
+																flags);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("function %s"), proname);
+				break;
+			}
 
 		case OCLASS_TYPE:
-			appendStringInfo(&buffer, _("type %s"),
-							 format_type_be(object->objectId));
-			break;
+			{
+				bits16		flags = FORMAT_TYPE_INVALID_AS_NULL;
+				char	   *typname = format_type_extended(object->objectId, -1,
+														   flags);
+
+				if (typname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("type %s"), typname);
+				break;
+			}
 
 		case OCLASS_CAST:
 			{
@@ -2906,8 +2935,17 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+					}
+
+					systable_endscan(rcscan);
+					table_close(castDesc, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -2929,8 +2967,15 @@ getObjectDescription(const ObjectAddress *object)
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+					{
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					}
+					break;
+				}
+
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 
 				/* Qualify the name if not visible in search path */
@@ -2954,8 +2999,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
+
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
@@ -2963,7 +3013,7 @@ getObjectDescription(const ObjectAddress *object)
 					StringInfoData rel;
 
 					initStringInfo(&rel);
-					getRelationDescription(&rel, con->conrelid);
+					getRelationDescription(&rel, con->conrelid, false);
 					/* translator: second %s is, e.g., "table %s" */
 					appendStringInfo(&buffer, _("constraint %s on %s"),
 									 NameStr(con->conname), rel.data);
@@ -2988,8 +3038,13 @@ getObjectDescription(const ObjectAddress *object)
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
+
 				conv = (Form_pg_conversion) GETSTRUCT(conTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3027,8 +3082,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					table_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -3038,7 +3100,7 @@ getObjectDescription(const ObjectAddress *object)
 
 				/* translator: %s is typically "column %s of table %s" */
 				appendStringInfo(&buffer, _("default value for %s"),
-								 getObjectDescription(&colobject));
+								 getObjectDescription(&colobject, false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -3046,19 +3108,35 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_LANGUAGE:
-			appendStringInfo(&buffer, _("language %s"),
-							 get_language_name(object->objectId, false));
-			break;
+			{
+				char	   *langname = get_language_name(object->objectId,
+														 missing_ok);
+
+				if (langname)
+					appendStringInfo(&buffer, _("language %s"),
+									 get_language_name(object->objectId, false));
+				break;
+			}
 
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, _("large object %u"),
 							 object->objectId);
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfo(&buffer, _("operator %s"),
-							 format_operator(object->objectId));
-			break;
+			{
+				bits16		flags = FORMAT_OPERATOR_INVALID_AS_NULL;
+				char	   *oprname = format_operator_extended(object->objectId,
+															   flags);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfo(&buffer, _("operator %s"), oprname);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -3071,8 +3149,13 @@ getObjectDescription(const ObjectAddress *object)
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
+
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 
 				amTup = SearchSysCache1(AMOID,
@@ -3099,7 +3182,7 @@ getObjectDescription(const ObjectAddress *object)
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyDescription(&buffer, object->objectId);
+			getOpFamilyDescription(&buffer, object->objectId, missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -3109,8 +3192,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(AMOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
+
 				appendStringInfo(&buffer, _("access method %s"),
 								 NameStr(((Form_pg_am) GETSTRUCT(tup))->amname));
 				ReleaseSysCache(tup);
@@ -3140,13 +3228,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amopForm->amopfamily);
+				getOpFamilyDescription(&opfam, amopForm->amopfamily, false);
 
 				/*------
 				   translator: %d is the operator strategy (a number), the
@@ -3190,13 +3285,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyDescription(&opfam, amprocForm->amprocfamily);
+				getOpFamilyDescription(&opfam, amprocForm->amprocfamily, false);
 
 				/*------
 				   translator: %d is the function number, the first two %s's
@@ -3239,12 +3341,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(ruleDesc, AccessShareLock);
+					break;
+				}
+
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, rule->ev_class);
+				getRelationDescription(&rel, rule->ev_class, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("rule %s on %s"),
@@ -3277,12 +3387,20 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(tgscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					systable_endscan(tgscan);
+					table_close(trigDesc, AccessShareLock);
+					break;
+				}
+
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, trig->tgrelid);
+				getRelationDescription(&rel, trig->tgrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("trigger %s on %s"),
@@ -3299,8 +3417,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				nspname = get_namespace_name(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("schema %s"), nspname);
 				break;
 			}
@@ -3314,8 +3436,13 @@ getObjectDescription(const ObjectAddress *object)
 				stxTup = SearchSysCache1(STATEXTOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(stxTup))
-					elog(ERROR, "could not find tuple for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for statistics object %u",
+							 object->objectId);
+					break;
+				}
+
 				stxForm = (Form_pg_statistic_ext) GETSTRUCT(stxTup);
 
 				/* Qualify the name if not visible in search path */
@@ -3341,8 +3468,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				prsForm = (Form_pg_ts_parser) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3367,8 +3498,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
+
 				dictForm = (Form_pg_ts_dict) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3393,8 +3529,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
+
 				tmplForm = (Form_pg_ts_template) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3419,8 +3560,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
+
 				cfgForm = (Form_pg_ts_config) GETSTRUCT(tup);
 
 				/* Qualify the name if not visible in search path */
@@ -3438,8 +3584,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_ROLE:
 			{
-				appendStringInfo(&buffer, _("role %s"),
-								 GetUserNameFromId(object->objectId, false));
+				char	   *username = GetUserNameFromId(object->objectId,
+														 missing_ok);
+
+				if (username)
+					appendStringInfo(&buffer, _("role %s"), username);
 				break;
 			}
 
@@ -3449,8 +3598,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("database %s"), datname);
 				break;
 			}
@@ -3461,8 +3614,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("tablespace %s"), tblspace);
 				break;
 			}
@@ -3471,8 +3628,10 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+					appendStringInfo(&buffer, _("foreign-data wrapper %s"), fdw->fdwname);
 				break;
 			}
 
@@ -3480,8 +3639,9 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfo(&buffer, _("server %s"), srv->servername);
+				srv = GetForeignServerExtended(object->objectId, missing_ok);
+				if (srv)
+					appendStringInfo(&buffer, _("server %s"), srv->servername);
 				break;
 			}
 
@@ -3496,8 +3656,13 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
+
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -3537,8 +3702,15 @@ getObjectDescription(const ObjectAddress *object)
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(defaclrel, AccessShareLock);
+					break;
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -3621,8 +3793,12 @@ getObjectDescription(const ObjectAddress *object)
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("extension %s"), extname);
 				break;
 			}
@@ -3634,8 +3810,12 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfo(&buffer, _("event trigger %s"),
 								 NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname));
 				ReleaseSysCache(tup);
@@ -3664,12 +3844,20 @@ getObjectDescription(const ObjectAddress *object)
 				tuple = systable_getnext(sscan);
 
 				if (!HeapTupleIsValid(tuple))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					systable_endscan(sscan);
+					table_close(policy_rel, AccessShareLock);
+					break;
+				}
+
 				form_policy = (Form_pg_policy) GETSTRUCT(tuple);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, form_policy->polrelid);
+				getRelationDescription(&rel, form_policy->polrelid, false);
 
 				/* translator: second %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("policy %s on %s"),
@@ -3682,9 +3870,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_PUBLICATION:
 			{
-				appendStringInfo(&buffer, _("publication %s"),
-								 get_publication_name(object->objectId,
-													  false));
+				char	   *pubname = get_publication_name(object->objectId,
+														   missing_ok);
+
+				if (pubname)
+					appendStringInfo(&buffer, _("publication %s"), pubname);
 				break;
 			}
 
@@ -3698,14 +3888,18 @@ getObjectDescription(const ObjectAddress *object)
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
 				initStringInfo(&rel);
-				getRelationDescription(&rel, prform->prrelid);
+				getRelationDescription(&rel, prform->prrelid, false);
 
 				/* translator: first %s is, e.g., "table %s" */
 				appendStringInfo(&buffer, _("publication of %s in publication %s"),
@@ -3717,9 +3911,11 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_SUBSCRIPTION:
 			{
-				appendStringInfo(&buffer, _("subscription %s"),
-								 get_subscription_name(object->objectId,
-													   false));
+				char	   *subname = get_subscription_name(object->objectId,
+															missing_ok);
+
+				if (subname)
+					appendStringInfo(&buffer, _("subscription %s"), subname);
 				break;
 			}
 
@@ -3731,8 +3927,12 @@ getObjectDescription(const ObjectAddress *object)
 				trfTup = SearchSysCache1(TRFOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(trfTup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+					break;
+				}
 
 				trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
 
@@ -3750,6 +3950,10 @@ getObjectDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty buffer is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -3765,7 +3969,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
 	address.objectId = objid;
 	address.objectSubId = 0;
 
-	return getObjectDescription(&address);
+	return getObjectDescription(&address, false);
 }
 
 /*
@@ -3774,7 +3978,7 @@ getObjectDescriptionOids(Oid classid, Oid objid)
  * The result is appended to "buffer".
  */
 static void
-getRelationDescription(StringInfo buffer, Oid relid)
+getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -3784,7 +3988,11 @@ getRelationDescription(StringInfo buffer, Oid relid)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	/* Qualify the name if not visible in search path */
@@ -3845,7 +4053,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
  * subroutine for getObjectDescription: describe an operator family
  */
 static void
-getOpFamilyDescription(StringInfo buffer, Oid opfid)
+getOpFamilyDescription(StringInfo buffer, Oid opfid, bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -3855,7 +4063,11 @@ getOpFamilyDescription(StringInfo buffer, Oid opfid)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -3899,7 +4111,11 @@ pg_describe_object(PG_FUNCTION_ARGS)
 	address.objectId = objid;
 	address.objectSubId = objsubid;
 
-	description = getObjectDescription(&address);
+	description = getObjectDescription(&address, true);
+
+	if (description == NULL)
+		PG_RETURN_NULL();
+
 	PG_RETURN_TEXT_P(cstring_to_text(description));
 }
 
@@ -3914,6 +4130,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 	int32		objsubid = PG_GETARG_INT32(2);
 	Oid			schema_oid = InvalidOid;
 	const char *objname = NULL;
+	char	   *objidentity;
 	ObjectAddress address;
 	Datum		values[4];
 	bool		nulls[4];
@@ -3988,12 +4205,18 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		table_close(catalog, AccessShareLock);
 	}
 
-	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	/* object type, which can never be NULL */
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
+	/*
+	 * Before doing anything, extract the object identity.  If the identity
+	 * could not be found, set all the fields except the object type to NULL.
+	 */
+	objidentity = getObjectIdentity(&address, true);
+
 	/* schema name */
-	if (OidIsValid(schema_oid))
+	if (OidIsValid(schema_oid) && objidentity)
 	{
 		const char *schema = quote_identifier(get_namespace_name(schema_oid));
 
@@ -4004,7 +4227,7 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[1] = true;
 
 	/* object name */
-	if (objname)
+	if (objname && objidentity)
 	{
 		values[2] = CStringGetTextDatum(objname);
 		nulls[2] = false;
@@ -4013,8 +4236,13 @@ pg_identify_object(PG_FUNCTION_ARGS)
 		nulls[2] = true;
 
 	/* object identity */
-	values[3] = CStringGetTextDatum(getObjectIdentity(&address));
-	nulls[3] = false;
+	if (objidentity)
+	{
+		values[3] = CStringGetTextDatum(objidentity);
+		nulls[3] = false;
+	}
+	else
+		nulls[3] = true;
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -4058,26 +4286,34 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
 	tupdesc = BlessTupleDesc(tupdesc);
 
 	/* object type */
-	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address));
+	values[0] = CStringGetTextDatum(getObjectTypeDescription(&address, true));
 	nulls[0] = false;
 
 	/* object identity */
-	identity = getObjectIdentityParts(&address, &names, &args);
-	pfree(identity);
-
-	/* object_names */
-	if (names != NIL)
-		values[1] = PointerGetDatum(strlist_to_textarray(names));
+	identity = getObjectIdentityParts(&address, &names, &args, true);
+	if (identity == NULL)
+	{
+		nulls[1] = true;
+		nulls[2] = true;
+	}
 	else
-		values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[1] = false;
+	{
+		pfree(identity);
 
-	/* object_args */
-	if (args)
-		values[2] = PointerGetDatum(strlist_to_textarray(args));
-	else
-		values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
-	nulls[2] = false;
+		/* object_names */
+		if (names != NIL)
+			values[1] = PointerGetDatum(strlist_to_textarray(names));
+		else
+			values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[1] = false;
+
+		/* object_args */
+		if (args)
+			values[2] = PointerGetDatum(strlist_to_textarray(args));
+		else
+			values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
+		nulls[2] = false;
+	}
 
 	htup = heap_form_tuple(tupdesc, values, nulls);
 
@@ -4091,7 +4327,7 @@ pg_identify_object_as_address(PG_FUNCTION_ARGS)
  * Keep ObjectTypeMap in sync with this.
  */
 char *
-getObjectTypeDescription(const ObjectAddress *object)
+getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4101,11 +4337,13 @@ getObjectTypeDescription(const ObjectAddress *object)
 	{
 		case OCLASS_CLASS:
 			getRelationTypeDescription(&buffer, object->objectId,
-									   object->objectSubId);
+									   object->objectSubId,
+									   missing_ok);
 			break;
 
 		case OCLASS_PROC:
-			getProcedureTypeDescription(&buffer, object->objectId);
+			getProcedureTypeDescription(&buffer, object->objectId,
+										missing_ok);
 			break;
 
 		case OCLASS_TYPE:
@@ -4121,7 +4359,8 @@ getObjectTypeDescription(const ObjectAddress *object)
 			break;
 
 		case OCLASS_CONSTRAINT:
-			getConstraintTypeDescription(&buffer, object->objectId);
+			getConstraintTypeDescription(&buffer, object->objectId,
+										 missing_ok);
 			break;
 
 		case OCLASS_CONVERSION:
@@ -4258,6 +4497,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			 */
 	}
 
+	/* an empty string is equivalent to no object found */
+	if (buffer.len == 0)
+		return NULL;
+
 	return buffer.data;
 }
 
@@ -4265,7 +4508,8 @@ getObjectTypeDescription(const ObjectAddress *object)
  * subroutine for getObjectTypeDescription: describe a relation type
  */
 static void
-getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
+getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId,
+						   bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -4273,7 +4517,14 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		/* fallback to "relation" for an undefined object */
+		appendStringInfoString(buffer, "relation");
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	switch (relForm->relkind)
@@ -4320,7 +4571,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
  * subroutine for getObjectTypeDescription: describe a constraint type
  */
 static void
-getConstraintTypeDescription(StringInfo buffer, Oid constroid)
+getConstraintTypeDescription(StringInfo buffer, Oid constroid, bool missing_ok)
 {
 	Relation	constrRel;
 	HeapTuple	constrTup;
@@ -4330,7 +4581,16 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
 	constrTup = get_catalog_object_by_oid(constrRel, Anum_pg_constraint_oid,
 										  constroid);
 	if (!HeapTupleIsValid(constrTup))
-		elog(ERROR, "cache lookup failed for constraint %u", constroid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for constraint %u", constroid);
+
+		table_close(constrRel, AccessShareLock);
+
+		/* fallback to "constraint" for an undefined object */
+		appendStringInfoString(buffer, "constraint");
+		return;
+	}
 
 	constrForm = (Form_pg_constraint) GETSTRUCT(constrTup);
 
@@ -4348,7 +4608,8 @@ getConstraintTypeDescription(StringInfo buffer, Oid constroid)
  * subroutine for getObjectTypeDescription: describe a procedure type
  */
 static void
-getProcedureTypeDescription(StringInfo buffer, Oid procid)
+getProcedureTypeDescription(StringInfo buffer, Oid procid,
+							bool missing_ok)
 {
 	HeapTuple	procTup;
 	Form_pg_proc procForm;
@@ -4356,7 +4617,14 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
 	procTup = SearchSysCache1(PROCOID,
 							  ObjectIdGetDatum(procid));
 	if (!HeapTupleIsValid(procTup))
-		elog(ERROR, "cache lookup failed for procedure %u", procid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure %u", procid);
+
+		/* fallback to "procedure" for an undefined object */
+		appendStringInfoString(buffer, "procedure");
+		return;
+	}
 	procForm = (Form_pg_proc) GETSTRUCT(procTup);
 
 	if (procForm->prokind == PROKIND_AGGREGATE)
@@ -4373,12 +4641,13 @@ getProcedureTypeDescription(StringInfo buffer, Oid procid)
  * Obtain a given object's identity, as a palloc'ed string.
  *
  * This is for machine consumption, so it's not translated.  All elements are
- * schema-qualified when appropriate.
+ * schema-qualified when appropriate.  Returns NULL if the object could not
+ * be found.
  */
 char *
-getObjectIdentity(const ObjectAddress *object)
+getObjectIdentity(const ObjectAddress *object, bool missing_ok)
 {
-	return getObjectIdentityParts(object, NULL, NULL);
+	return getObjectIdentityParts(object, NULL, NULL, missing_ok);
 }
 
 /*
@@ -4387,11 +4656,13 @@ getObjectIdentity(const ObjectAddress *object)
  * There are two sets of return values: the identity itself as a palloc'd
  * string is returned.  objname and objargs, if not NULL, are output parameters
  * that receive lists of C-strings that are useful to give back to
- * get_object_address() to reconstruct the ObjectAddress.
+ * get_object_address() to reconstruct the ObjectAddress.  Returns NULL if
+ * the object could not be found.
  */
 char *
 getObjectIdentityParts(const ObjectAddress *object,
-					   List **objname, List **objargs)
+					   List **objname, List **objargs,
+					   bool missing_ok)
 {
 	StringInfoData buffer;
 
@@ -4413,31 +4684,64 @@ getObjectIdentityParts(const ObjectAddress *object,
 	switch (getObjectClass(object))
 	{
 		case OCLASS_CLASS:
-			getRelationIdentity(&buffer, object->objectId, objname);
-			if (object->objectSubId != 0)
 			{
-				char	   *attr;
+				char	   *attr = NULL;
 
-				attr = get_attname(object->objectId, object->objectSubId,
-								   false);
-				appendStringInfo(&buffer, ".%s", quote_identifier(attr));
-				if (objname)
-					*objname = lappend(*objname, attr);
+				/*
+				 * Check for the attribute first, so as if it is missing we
+				 * can skip the entire relation description.
+				 */
+				if (object->objectSubId != 0)
+				{
+					attr = get_attname(object->objectId,
+									   object->objectSubId,
+									   missing_ok);
+
+					if (missing_ok && attr == NULL)
+						break;
+				}
+
+				getRelationIdentity(&buffer, object->objectId, objname,
+									missing_ok);
+				if (objname && *objname == NIL)
+					break;
+
+				if (attr)
+				{
+					appendStringInfo(&buffer, ".%s",
+									 quote_identifier(attr));
+					if (objname)
+						*objname = lappend(*objname, attr);
+				}
 			}
 			break;
 
 		case OCLASS_PROC:
-			appendStringInfoString(&buffer,
-								   format_procedure_qualified(object->objectId));
-			if (objname)
-				format_procedure_parts(object->objectId, objname, objargs);
-			break;
+			{
+				bits16		flags = FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL;
+				char	   *proname = format_procedure_extended(object->objectId,
+																flags);
+
+				if (proname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, proname);
+				if (objname)
+					format_procedure_parts(object->objectId, objname, objargs,
+										   missing_ok);
+				break;
+			}
 
 		case OCLASS_TYPE:
 			{
+				bits16		flags = FORMAT_TYPE_INVALID_AS_NULL | FORMAT_TYPE_FORCE_QUALIFY;
 				char	   *typeout;
 
-				typeout = format_type_be_qualified(object->objectId);
+				typeout = format_type_extended(object->objectId, -1, flags);
+
+				if (typeout == NULL)
+					break;
+
 				appendStringInfoString(&buffer, typeout);
 				if (objname)
 					*objname = list_make1(typeout);
@@ -4456,8 +4760,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for cast %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for cast %u",
+							 object->objectId);
+
+					table_close(castRel, AccessShareLock);
+					break;
+				}
 
 				castForm = (Form_pg_cast) GETSTRUCT(tup);
 
@@ -4484,8 +4794,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				collTup = SearchSysCache1(COLLOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(collTup))
-					elog(ERROR, "cache lookup failed for collation %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for collation %u",
+							 object->objectId);
+					break;
+				}
 				coll = (Form_pg_collation) GETSTRUCT(collTup);
 				schema = get_namespace_name_or_temp(coll->collnamespace);
 				appendStringInfoString(&buffer,
@@ -4506,15 +4820,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONSTROID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for constraint %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for constraint %u",
+							 object->objectId);
+					break;
+				}
 				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
 					appendStringInfo(&buffer, "%s on ",
 									 quote_identifier(NameStr(con->conname)));
-					getRelationIdentity(&buffer, con->conrelid, objname);
+					getRelationIdentity(&buffer, con->conrelid, objname,
+										false);
 					if (objname)
 						*objname = lappend(*objname, pstrdup(NameStr(con->conname)));
 				}
@@ -4529,7 +4848,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 					appendStringInfo(&buffer, "%s on %s",
 									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname, objargs));
+									 getObjectIdentityParts(&domain, objname,
+															objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -4548,8 +4868,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				conTup = SearchSysCache1(CONVOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(conTup))
-					elog(ERROR, "cache lookup failed for conversion %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for conversion %u",
+							 object->objectId);
+					break;
+				}
 				conForm = (Form_pg_conversion) GETSTRUCT(conTup);
 				schema = get_namespace_name_or_temp(conForm->connamespace);
 				appendStringInfoString(&buffer,
@@ -4585,8 +4909,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(adscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for attrdef %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for attrdef %u",
+							 object->objectId);
+
+					systable_endscan(adscan);
+					table_close(attrdefDesc, AccessShareLock);
+					break;
+				}
 
 				attrdef = (Form_pg_attrdef) GETSTRUCT(tup);
 
@@ -4596,7 +4927,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				appendStringInfo(&buffer, "for %s",
 								 getObjectIdentityParts(&colobject,
-														objname, objargs));
+														objname, objargs,
+														false));
 
 				systable_endscan(adscan);
 				table_close(attrdefDesc, AccessShareLock);
@@ -4611,8 +4943,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				langTup = SearchSysCache1(LANGOID,
 										  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(langTup))
-					elog(ERROR, "cache lookup failed for language %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for language %u",
+							 object->objectId);
+					break;
+				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(langForm->lanname)));
@@ -4622,6 +4958,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 		case OCLASS_LARGEOBJECT:
+			if (!LargeObjectExists(object->objectId))
+				break;
 			appendStringInfo(&buffer, "%u",
 							 object->objectId);
 			if (objname)
@@ -4629,11 +4967,19 @@ getObjectIdentityParts(const ObjectAddress *object,
 			break;
 
 		case OCLASS_OPERATOR:
-			appendStringInfoString(&buffer,
-								   format_operator_qualified(object->objectId));
-			if (objname)
-				format_operator_parts(object->objectId, objname, objargs);
-			break;
+			{
+				bits16		flags = FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_INVALID_AS_NULL;
+				char	   *oprname = format_operator_extended(object->objectId,
+															   flags);
+
+				if (oprname == NULL)
+					break;
+
+				appendStringInfoString(&buffer, oprname);
+				if (objname)
+					format_operator_parts(object->objectId, objname, objargs, missing_ok);
+				break;
+			}
 
 		case OCLASS_OPCLASS:
 			{
@@ -4646,8 +4992,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				opcTup = SearchSysCache1(CLAOID,
 										 ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(opcTup))
-					elog(ERROR, "cache lookup failed for opclass %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for opclass %u",
+							 object->objectId);
+					break;
+				}
 				opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
 				schema = get_namespace_name_or_temp(opcForm->opcnamespace);
 
@@ -4673,7 +5023,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 			}
 
 		case OCLASS_OPFAMILY:
-			getOpFamilyIdentity(&buffer, object->objectId, objname);
+			getOpFamilyIdentity(&buffer, object->objectId, objname,
+								missing_ok);
 			break;
 
 		case OCLASS_AM:
@@ -4682,8 +5033,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				amname = get_am_name(object->objectId);
 				if (!amname)
-					elog(ERROR, "cache lookup failed for access method %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for access method %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(amname));
 				if (objname)
 					*objname = list_make1(amname);
@@ -4715,13 +5070,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amop entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amop entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amopDesc, AccessShareLock);
+					break;
+				}
 
 				amopForm = (Form_pg_amop) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname);
+				getOpFamilyIdentity(&opfam, amopForm->amopfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amopForm->amoplefttype);
 				rtype = format_type_be_qualified(amopForm->amoprighttype);
@@ -4769,13 +5132,21 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(amscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for amproc entry %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for amproc entry %u",
+							 object->objectId);
+
+					systable_endscan(amscan);
+					table_close(amprocDesc, AccessShareLock);
+					break;
+				}
 
 				amprocForm = (Form_pg_amproc) GETSTRUCT(tup);
 
 				initStringInfo(&opfam);
-				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname);
+				getOpFamilyIdentity(&opfam, amprocForm->amprocfamily, objname,
+									false);
 
 				ltype = format_type_be_qualified(amprocForm->amproclefttype);
 				rtype = format_type_be_qualified(amprocForm->amprocrighttype);
@@ -4810,14 +5181,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for rule %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for rule %u",
+							 object->objectId);
+
+					table_close(ruleDesc, AccessShareLock);
+					break;
+				}
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(rule->rulename)));
-				getRelationIdentity(&buffer, rule->ev_class, objname);
+				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
 
@@ -4837,14 +5214,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for trigger %u",
+							 object->objectId);
+
+					table_close(trigDesc, AccessShareLock);
+					break;
+				}
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(trig->tgname)));
-				getRelationIdentity(&buffer, trig->tgrelid, objname);
+				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
 
@@ -4858,8 +5241,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				nspname = get_namespace_name_or_temp(object->objectId);
 				if (!nspname)
-					elog(ERROR, "cache lookup failed for namespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for namespace %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer,
 									   quote_identifier(nspname));
 				if (objname)
@@ -4876,8 +5263,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(STATEXTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for statistics object %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for statistics object %u",
+							 object->objectId);
+					break;
+				}
 				formStatistic = (Form_pg_statistic_ext) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formStatistic->stxnamespace);
 				appendStringInfoString(&buffer,
@@ -4899,8 +5290,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSPARSEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search parser %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search parser %u",
+							 object->objectId);
+					break;
+				}
 				formParser = (Form_pg_ts_parser) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formParser->prsnamespace);
 				appendStringInfoString(&buffer,
@@ -4922,8 +5317,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSDICTOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search dictionary %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search dictionary %u",
+							 object->objectId);
+					break;
+				}
 				formDict = (Form_pg_ts_dict) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formDict->dictnamespace);
 				appendStringInfoString(&buffer,
@@ -4945,8 +5344,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSTEMPLATEOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search template %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search template %u",
+							 object->objectId);
+					break;
+				}
 				formTmpl = (Form_pg_ts_template) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formTmpl->tmplnamespace);
 				appendStringInfoString(&buffer,
@@ -4968,8 +5371,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(TSCONFIGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for text search configuration %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for text search configuration %u",
+							 object->objectId);
+					break;
+				}
 				formCfg = (Form_pg_ts_config) GETSTRUCT(tup);
 				schema = get_namespace_name_or_temp(formCfg->cfgnamespace);
 				appendStringInfoString(&buffer,
@@ -4986,7 +5393,9 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *username;
 
-				username = GetUserNameFromId(object->objectId, false);
+				username = GetUserNameFromId(object->objectId, missing_ok);
+				if (!username)
+					break;
 				if (objname)
 					*objname = list_make1(username);
 				appendStringInfoString(&buffer,
@@ -5000,8 +5409,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				datname = get_database_name(object->objectId);
 				if (!datname)
-					elog(ERROR, "cache lookup failed for database %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for database %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(datname);
 				appendStringInfoString(&buffer,
@@ -5015,8 +5428,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				tblspace = get_tablespace_name(object->objectId);
 				if (!tblspace)
-					elog(ERROR, "cache lookup failed for tablespace %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for tablespace %u",
+							 object->objectId);
+					break;
+				}
 				if (objname)
 					*objname = list_make1(tblspace);
 				appendStringInfoString(&buffer,
@@ -5028,10 +5445,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignDataWrapper *fdw;
 
-				fdw = GetForeignDataWrapper(object->objectId);
-				appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
-				if (objname)
-					*objname = list_make1(pstrdup(fdw->fdwname));
+				fdw = GetForeignDataWrapperExtended(object->objectId,
+													missing_ok);
+				if (fdw)
+				{
+					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					if (objname)
+						*objname = list_make1(pstrdup(fdw->fdwname));
+				}
 				break;
 			}
 
@@ -5039,11 +5460,15 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				ForeignServer *srv;
 
-				srv = GetForeignServer(object->objectId);
-				appendStringInfoString(&buffer,
-									   quote_identifier(srv->servername));
-				if (objname)
-					*objname = list_make1(pstrdup(srv->servername));
+				srv = GetForeignServerExtended(object->objectId,
+											   missing_ok);
+				if (srv)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(srv->servername));
+					if (objname)
+						*objname = list_make1(pstrdup(srv->servername));
+				}
 				break;
 			}
 
@@ -5058,8 +5483,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(USERMAPPINGOID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for user mapping %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for user mapping %u",
+							 object->objectId);
+					break;
+				}
 				umform = (Form_pg_user_mapping) GETSTRUCT(tup);
 				useid = umform->umuser;
 				srv = GetForeignServer(umform->umserver);
@@ -5106,8 +5535,16 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = systable_getnext(rcscan);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for default ACL %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for default ACL %u",
+							 object->objectId);
+
+					systable_endscan(rcscan);
+					table_close(defaclrel, AccessShareLock);
+					break;
+
+				}
 
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
@@ -5169,8 +5606,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				extname = get_extension_name(object->objectId);
 				if (!extname)
-					elog(ERROR, "cache lookup failed for extension %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for extension %u",
+							 object->objectId);
+					break;
+				}
 				appendStringInfoString(&buffer, quote_identifier(extname));
 				if (objname)
 					*objname = list_make1(extname);
@@ -5189,8 +5630,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(EVENTTRIGGEROID,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for event trigger %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for event trigger %u",
+							 object->objectId);
+					break;
+				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				appendStringInfoString(&buffer,
 									   quote_identifier(NameStr(trigForm->evtname)));
@@ -5210,14 +5655,20 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for policy %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for policy %u",
+							 object->objectId);
+
+					table_close(polDesc, AccessShareLock);
+					break;
+				}
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
 				appendStringInfo(&buffer, "%s on ",
 								 quote_identifier(NameStr(policy->polname)));
-				getRelationIdentity(&buffer, policy->polrelid, objname);
+				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
 
@@ -5229,11 +5680,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *pubname;
 
-				pubname = get_publication_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(pubname));
-				if (objname)
-					*objname = list_make1(pubname);
+				pubname = get_publication_name(object->objectId, missing_ok);
+				if (pubname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(pubname));
+					if (objname)
+						*objname = list_make1(pubname);
+				}
 				break;
 			}
 
@@ -5246,13 +5700,17 @@ getObjectIdentityParts(const ObjectAddress *object,
 				tup = SearchSysCache1(PUBLICATIONREL,
 									  ObjectIdGetDatum(object->objectId));
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "cache lookup failed for publication table %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for publication table %u",
+							 object->objectId);
+					break;
+				}
 
 				prform = (Form_pg_publication_rel) GETSTRUCT(tup);
 				pubname = get_publication_name(prform->prpubid, false);
 
-				getRelationIdentity(&buffer, prform->prrelid, objname);
+				getRelationIdentity(&buffer, prform->prrelid, objname, false);
 				appendStringInfo(&buffer, " in publication %s", pubname);
 
 				if (objargs)
@@ -5266,11 +5724,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 			{
 				char	   *subname;
 
-				subname = get_subscription_name(object->objectId, false);
-				appendStringInfoString(&buffer,
-									   quote_identifier(subname));
-				if (objname)
-					*objname = list_make1(subname);
+				subname = get_subscription_name(object->objectId, missing_ok);
+				if (subname)
+				{
+					appendStringInfoString(&buffer,
+										   quote_identifier(subname));
+					if (objname)
+						*objname = list_make1(subname);
+				}
 				break;
 			}
 
@@ -5289,8 +5750,14 @@ getObjectIdentityParts(const ObjectAddress *object,
 												object->objectId);
 
 				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for transform %u",
-						 object->objectId);
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for transform %u",
+							 object->objectId);
+
+					table_close(transformDesc, AccessShareLock);
+					break;
+				}
 
 				transform = (Form_pg_transform) GETSTRUCT(tup);
 
@@ -5316,20 +5783,34 @@ getObjectIdentityParts(const ObjectAddress *object,
 			 */
 	}
 
-	/*
-	 * If a get_object_address representation was requested, make sure we are
-	 * providing one.  We don't check objargs, because many of the cases above
-	 * leave it as NIL.
-	 */
-	if (objname && *objname == NIL)
-		elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
-			 (int) getObjectClass(object), buffer.data);
+	if (!missing_ok)
+	{
+		/*
+		 * If a get_object_address representation was requested, make sure we
+		 * are providing one.  We don't check objargs, because many of the
+		 * cases above leave it as NIL.
+		 */
+		if (objname && *objname == NIL)
+			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
+				 (int) getObjectClass(object), buffer.data);
+	}
+	else
+	{
+		/* an empty buffer is equivalent to no object found */
+		if (buffer.len == 0)
+		{
+			Assert((objname == NULL || *objname == NIL) &&
+				   (objargs == NULL || *objargs == NIL));
+			return NULL;
+		}
+	}
 
 	return buffer.data;
 }
 
 static void
-getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
+getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	opfTup;
 	Form_pg_opfamily opfForm;
@@ -5339,7 +5820,11 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
 
 	opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid));
 	if (!HeapTupleIsValid(opfTup))
-		elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for opfamily %u", opfid);
+		return;
+	}
 	opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup);
 
 	amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod));
@@ -5368,7 +5853,8 @@ getOpFamilyIdentity(StringInfo buffer, Oid opfid, List **object)
  * StringInfo.
  */
 static void
-getRelationIdentity(StringInfo buffer, Oid relid, List **object)
+getRelationIdentity(StringInfo buffer, Oid relid, List **object,
+					bool missing_ok)
 {
 	HeapTuple	relTup;
 	Form_pg_class relForm;
@@ -5377,7 +5863,14 @@ getRelationIdentity(StringInfo buffer, Oid relid, List **object)
 	relTup = SearchSysCache1(RELOID,
 							 ObjectIdGetDatum(relid));
 	if (!HeapTupleIsValid(relTup))
-		elog(ERROR, "cache lookup failed for relation %u", relid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		if (object)
+			*object = NIL;
+		return;
+	}
 	relForm = (Form_pg_class) GETSTRUCT(relTup);
 
 	schema = get_namespace_name_or_temp(relForm->relnamespace);
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 21cfdcace9..70baf03178 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -160,7 +160,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 				ereport(ERROR,
 						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 						 errmsg("%s is already a member of extension \"%s\"",
-								getObjectDescription(object),
+								getObjectDescription(object, false),
 								get_extension_name(oldext))));
 			}
 		}
@@ -536,7 +536,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot remove dependency on %s because it is a system object",
-						getObjectDescription(&objAddr))));
+						getObjectDescription(&objAddr, false))));
 
 	/*
 	 * We can handle adding a dependency on something pinned, though, since
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index f776e821b3..082b935a69 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -638,7 +638,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop %s because it is required by the database system",
-							getObjectDescription(&object))));
+							getObjectDescription(&object, false))));
 		}
 
 		object.classId = sdepForm->classid;
@@ -1147,7 +1147,7 @@ storeObjectDescription(StringInfo descs,
 					   SharedDependencyType deptype,
 					   int count)
 {
-	char	   *objdesc = getObjectDescription(object);
+	char	   *objdesc = getObjectDescription(object, false);
 
 	/* separate entries with a newline */
 	if (descs->len != 0)
@@ -1283,7 +1283,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot drop objects owned by %s because they are "
 							"required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 		}
 
 		ScanKeyInit(&key[0],
@@ -1429,7 +1429,7 @@ shdepReassignOwned(List *roleids, Oid newrole)
 			ereport(ERROR,
 					(errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
 					 errmsg("cannot reassign ownership of objects owned by %s because they are required by the database system",
-							getObjectDescription(&obj))));
+							getObjectDescription(&obj, false))));
 
 			/*
 			 * There's no need to tell the whole truth, which is that we
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 151cdfff39..7844880170 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1267,10 +1267,11 @@ EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool no
 
 	/* object identity, objname and objargs */
 	obj->objidentity =
-		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs);
+		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
+							   false);
 
 	/* object type */
-	obj->objecttype = getObjectTypeDescription(&obj->address);
+	obj->objecttype = getObjectTypeDescription(&obj->address, false);
 
 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
 
@@ -1929,8 +1930,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
 					else if (cmd->type == SCT_AlterTSConfig)
 						addr = cmd->d.atscfg.address;
 
-					type = getObjectTypeDescription(&addr);
-					identity = getObjectIdentity(&addr);
+					type = getObjectTypeDescription(&addr, false);
+					identity = getObjectIdentity(&addr, false);
 
 					/*
 					 * Obtain schema name, if any ("pg_temp" if a temp
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 3b69ab7ed5..c796fcd8da 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2919,7 +2919,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 					 errmsg("extension \"%s\" does not support SET SCHEMA",
 							NameStr(extForm->extname)),
 					 errdetail("%s is not in the extension's schema \"%s\"",
-							   getObjectDescription(&dep),
+							   getObjectDescription(&dep, false),
 							   get_namespace_name(oldNspOid))));
 	}
 
@@ -3328,7 +3328,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is already a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							get_extension_name(oldExtension))));
 
 		/*
@@ -3368,7 +3368,7 @@ ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("%s is not a member of extension \"%s\"",
-							getObjectDescription(&object),
+							getObjectDescription(&object, false),
 							stmt->extname)));
 
 		/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f79044f39f..d651f7cb1d 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -11359,7 +11359,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 					{
 						/* Not expecting any other direct dependencies... */
 						elog(ERROR, "unexpected object depending on column: %s",
-							 getObjectDescription(&foundObject));
+							 getObjectDescription(&foundObject, false));
 					}
 					break;
 				}
@@ -11375,7 +11375,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used by a view or rule"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -11394,7 +11394,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a trigger definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -11412,7 +11412,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("cannot alter type of a column used in a policy definition"),
 						 errdetail("%s depends on column \"%s\"",
-								   getObjectDescription(&foundObject),
+								   getObjectDescription(&foundObject, false),
 								   colName)));
 				break;
 
@@ -11473,7 +11473,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				 * a column.
 				 */
 				elog(ERROR, "unexpected object depending on column: %s",
-					 getObjectDescription(&foundObject));
+					 getObjectDescription(&foundObject, false));
 				break;
 
 				/*
@@ -11529,7 +11529,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			  foundDep->refobjsubid != 0)
 			)
 			elog(ERROR, "found unexpected dependency for column: %s",
-				 getObjectDescription(&foundObject));
+				 getObjectDescription(&foundObject, false));
 
 		CatalogTupleDelete(depRel, &depTup->t_self);
 	}
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index b41189db5c..6c1ee9c92d 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -418,7 +418,8 @@ format_procedure_extended(Oid procedure_oid, bits16 flags)
  * This can be used to feed get_object_address.
  */
 void
-format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
+format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs,
+					   bool missing_ok)
 {
 	HeapTuple	proctup;
 	Form_pg_proc procform;
@@ -428,7 +429,11 @@ format_procedure_parts(Oid procedure_oid, List **objnames, List **objargs)
 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procedure_oid));
 
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for procedure with OID %u", procedure_oid);
+		return;
+	}
 
 	procform = (Form_pg_proc) GETSTRUCT(proctup);
 	nargs = procform->pronargs;
@@ -856,15 +861,20 @@ format_operator_qualified(Oid operator_oid)
 }
 
 void
-format_operator_parts(Oid operator_oid, List **objnames, List **objargs)
+format_operator_parts(Oid operator_oid, List **objnames, List **objargs,
+					  bool missing_ok)
 {
 	HeapTuple	opertup;
 	Form_pg_operator oprForm;
 
 	opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operator_oid));
 	if (!HeapTupleIsValid(opertup))
-		elog(ERROR, "cache lookup failed for operator with OID %u",
-			 operator_oid);
+	{
+		if (!missing_ok)
+			elog(ERROR, "cache lookup failed for operator with OID %u",
+				 operator_oid);
+		return;
+	}
 
 	oprForm = (Form_pg_operator) GETSTRUCT(opertup);
 	*objnames = list_make2(get_namespace_name_or_temp(oprForm->oprnamespace),
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index d6d1470156..d129501740 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -521,3 +521,101 @@ drop cascades to function trig()
 drop cascades to function proc(integer)
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Keep this list in the same order as getObjectIdentityParts()
+WITH objects (classid, objid, objsubid) AS (VALUES
+    ('pg_class'::regclass, 0, 0), -- no relation
+    ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+    ('pg_proc'::regclass, 0, 0), -- no function
+    ('pg_type'::regclass, 0, 0), -- no type
+    ('pg_cast'::regclass, 0, 0), -- no cast
+    ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_constraint'::regclass, 0, 0), -- no constraint
+    ('pg_conversion'::regclass, 0, 0), -- no conversion
+    ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+    ('pg_language'::regclass, 0, 0), -- no language
+    ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+    ('pg_operator'::regclass, 0, 0), -- no operator
+    ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+    ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+    ('pg_am'::regclass, 0, 0), -- no access method
+    ('pg_amop'::regclass, 0, 0), -- no AM operator
+    ('pg_amproc'::regclass, 0, 0), -- no AM proc
+    ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+    ('pg_trigger'::regclass, 0, 0), -- no trigger
+    ('pg_namespace'::regclass, 0, 0), -- no schema
+    ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+    ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+    ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+    ('pg_ts_template'::regclass, 0, 0), -- no TS template
+    ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+    ('pg_authid'::regclass, 0, 0), -- no role
+    ('pg_database'::regclass, 0, 0), -- no database
+    ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+    ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+    ('pg_foreign_server'::regclass, 0, 0), -- no server
+    ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+    ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+    ('pg_extension'::regclass, 0, 0), -- no extension
+    ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+    ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_publication'::regclass, 0, 0), -- no publication
+    ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+    ('pg_subscription'::regclass, 0, 0), -- no subscription
+    ('pg_transform'::regclass, 0, 0) -- no transformation
+  )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+         AS ident,
+       ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+         AS addr,
+       pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+         AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
+                 ident                  |                 addr                  | descr 
+----------------------------------------+---------------------------------------+-------
+ ("(""default acl"",,,)")               | ("(""default acl"",,)")               | NULL
+ ("(tablespace,,,)")                    | ("(tablespace,,)")                    | NULL
+ ("(type,,,)")                          | ("(type,,)")                          | NULL
+ ("(procedure,,,)")                     | ("(procedure,,)")                     | NULL
+ ("(relation,,,)")                      | ("(relation,,)")                      | NULL
+ ("(""table column"",,,)")              | ("(""table column"",,)")              | NULL
+ ("(role,,,)")                          | ("(role,,)")                          | NULL
+ ("(database,,,)")                      | ("(database,,)")                      | NULL
+ ("(server,,,)")                        | ("(server,,)")                        | NULL
+ ("(""user mapping"",,,)")              | ("(""user mapping"",,)")              | NULL
+ ("(""foreign-data wrapper"",,,)")      | ("(""foreign-data wrapper"",,)")      | NULL
+ ("(""access method"",,,)")             | ("(""access method"",,)")             | NULL
+ ("(""operator of access method"",,,)") | ("(""operator of access method"",,)") | NULL
+ ("(""function of access method"",,,)") | ("(""function of access method"",,)") | NULL
+ ("(""default value"",,,)")             | ("(""default value"",,)")             | NULL
+ ("(cast,,,)")                          | ("(cast,,)")                          | NULL
+ ("(constraint,,,)")                    | ("(constraint,,)")                    | NULL
+ ("(conversion,,,)")                    | ("(conversion,,)")                    | NULL
+ ("(language,,,)")                      | ("(language,,)")                      | NULL
+ ("(""large object"",,,)")              | ("(""large object"",,)")              | NULL
+ ("(schema,,,)")                        | ("(schema,,)")                        | NULL
+ ("(""operator class"",,,)")            | ("(""operator class"",,)")            | NULL
+ ("(operator,,,)")                      | ("(operator,,)")                      | NULL
+ ("(rule,,,)")                          | ("(rule,,)")                          | NULL
+ ("(trigger,,,)")                       | ("(trigger,,)")                       | NULL
+ ("(""operator family"",,,)")           | ("(""operator family"",,)")           | NULL
+ ("(extension,,,)")                     | ("(extension,,)")                     | NULL
+ ("(policy,,,)")                        | ("(policy,,)")                        | NULL
+ ("(""statistics object"",,,)")         | ("(""statistics object"",,)")         | NULL
+ ("(collation,,,)")                     | ("(collation,,)")                     | NULL
+ ("(""event trigger"",,,)")             | ("(""event trigger"",,)")             | NULL
+ ("(transform,,,)")                     | ("(transform,,)")                     | NULL
+ ("(""text search dictionary"",,,)")    | ("(""text search dictionary"",,)")    | NULL
+ ("(""text search parser"",,,)")        | ("(""text search parser"",,)")        | NULL
+ ("(""text search configuration"",,,)") | ("(""text search configuration"",,)") | NULL
+ ("(""text search template"",,,)")      | ("(""text search template"",,)")      | NULL
+ ("(subscription,,,)")                  | ("(subscription,,)")                  | NULL
+ ("(publication,,,)")                   | ("(publication,,)")                   | NULL
+ ("(""publication relation"",,,)")      | ("(""publication relation"",,)")      | NULL
+(39 rows)
+
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 8e06248eb5..cec44b2731 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -221,3 +221,59 @@ DROP SCHEMA addr_nsp CASCADE;
 
 DROP OWNED BY regress_addr_user;
 DROP USER regress_addr_user;
+
+--
+-- Checks for invalid objects
+--
+-- Make sure that NULL handling is correct.
+\pset null 'NULL'
+-- Keep this list in the same order as getObjectIdentityParts()
+WITH objects (classid, objid, objsubid) AS (VALUES
+    ('pg_class'::regclass, 0, 0), -- no relation
+    ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation
+    ('pg_proc'::regclass, 0, 0), -- no function
+    ('pg_type'::regclass, 0, 0), -- no type
+    ('pg_cast'::regclass, 0, 0), -- no cast
+    ('pg_collation'::regclass, 0, 0), -- no collation
+    ('pg_constraint'::regclass, 0, 0), -- no constraint
+    ('pg_conversion'::regclass, 0, 0), -- no conversion
+    ('pg_attrdef'::regclass, 0, 0), -- no default attribute
+    ('pg_language'::regclass, 0, 0), -- no language
+    ('pg_largeobject'::regclass, 0, 0), -- no large object, no error
+    ('pg_operator'::regclass, 0, 0), -- no operator
+    ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method
+    ('pg_opfamily'::regclass, 0, 0), -- no opfamily
+    ('pg_am'::regclass, 0, 0), -- no access method
+    ('pg_amop'::regclass, 0, 0), -- no AM operator
+    ('pg_amproc'::regclass, 0, 0), -- no AM proc
+    ('pg_rewrite'::regclass, 0, 0), -- no rewrite
+    ('pg_trigger'::regclass, 0, 0), -- no trigger
+    ('pg_namespace'::regclass, 0, 0), -- no schema
+    ('pg_statistic_ext'::regclass, 0, 0), -- no statistics
+    ('pg_ts_parser'::regclass, 0, 0), -- no TS parser
+    ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionnary
+    ('pg_ts_template'::regclass, 0, 0), -- no TS template
+    ('pg_ts_config'::regclass, 0, 0), -- no TS configuration
+    ('pg_authid'::regclass, 0, 0), -- no role
+    ('pg_database'::regclass, 0, 0), -- no database
+    ('pg_tablespace'::regclass, 0, 0), -- no tablespace
+    ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW
+    ('pg_foreign_server'::regclass, 0, 0), -- no server
+    ('pg_user_mapping'::regclass, 0, 0), -- no user mapping
+    ('pg_default_acl'::regclass, 0, 0), -- no default ACL
+    ('pg_extension'::regclass, 0, 0), -- no extension
+    ('pg_event_trigger'::regclass, 0, 0), -- no event trigger
+    ('pg_policy'::regclass, 0, 0), -- no policy
+    ('pg_publication'::regclass, 0, 0), -- no publication
+    ('pg_publication_rel'::regclass, 0, 0), -- no publication relation
+    ('pg_subscription'::regclass, 0, 0), -- no subscription
+    ('pg_transform'::regclass, 0, 0) -- no transformation
+  )
+SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid))
+         AS ident,
+       ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid))
+         AS addr,
+       pg_describe_object(objects.classid, objects.objid, objects.objsubid)
+         AS descr
+FROM objects
+ORDER BY objects.classid, objects.objid, objects.objsubid;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f065856535..2d72a3390c 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22826,7 +22826,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
         object).  This description is intended to be human-readable, and might
         be translated, depending on server configuration.  This is especially
         useful to determine the identity of an object referenced in the
-        <structname>pg_depend</structname> catalog.
+        <structname>pg_depend</structname> catalog. This function returns
+        <literal>NULL</literal> values for undefined objects.
        </para></entry>
       </row>
 
@@ -22858,7 +22859,8 @@ SELECT collation for ('foo' COLLATE "de_DE");
         otherwise <literal>NULL</literal>;
         <parameter>identity</parameter> is the complete object identity, with
         the precise format depending on object type, and each name within the
-        format being schema-qualified and quoted as necessary.
+        format being schema-qualified and quoted as necessary. Undefined
+        objects are identified with <literal>NULL</literal> values.
        </para></entry>
       </row>
 
@@ -22915,6 +22917,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
         <parameter>objsubid</parameter> is the sub-object ID, or zero if none.
         This function is the inverse
         of <function>pg_identify_object_as_address</function>.
+        Undefined objects are identified with <literal>NULL</literal> values.
        </para></entry>
       </row>
      </tbody>
diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c
index 4e83b7bfa8..ec20378593 100644
--- a/contrib/sepgsql/database.c
+++ b/contrib/sepgsql/database.c
@@ -142,7 +142,7 @@ sepgsql_database_drop(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -169,7 +169,7 @@ sepgsql_database_setattr(Oid databaseId)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_DATABASE,
@@ -193,7 +193,7 @@ sepgsql_database_relabel(Oid databaseId, const char *seclabel)
 	object.classId = DatabaseRelationId;
 	object.objectId = databaseId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_database:{setattr relabelfrom} permission
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index 53f6f41c5c..75ee612bcd 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -179,7 +179,7 @@ check_relation_privileges(Oid relOid,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -256,7 +256,7 @@ check_relation_privileges(Oid relOid,
 		object.classId = RelationRelationId;
 		object.objectId = relOid;
 		object.objectSubId = attnum;
-		audit_name = getObjectDescription(&object);
+		audit_name = getObjectDescription(&object, false);
 
 		result = sepgsql_avc_check_perms(&object,
 										 SEPG_CLASS_DB_COLUMN,
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 147ab67f32..32e405530b 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -355,7 +355,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event,
 					sepgsql_avc_check_perms(&object,
 											SEPG_CLASS_DB_PROCEDURE,
 											SEPG_DB_PROCEDURE__ENTRYPOINT,
-											getObjectDescription(&object),
+											getObjectDescription(&object, false),
 											true);
 
 					sepgsql_avc_check_perms_label(stack->new_label,
@@ -523,7 +523,7 @@ sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("sepgsql provider does not support labels on %s",
-							getObjectTypeDescription(object))));
+							getObjectTypeDescription(object, false))));
 			break;
 	}
 }
diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c
index 2c244a9003..d5d7dbe103 100644
--- a/contrib/sepgsql/proc.c
+++ b/contrib/sepgsql/proc.c
@@ -80,7 +80,7 @@ sepgsql_proc_post_create(Oid functionId)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	/*
@@ -114,7 +114,7 @@ sepgsql_proc_post_create(Oid functionId)
 		object.classId = TypeRelationId;
 		object.objectId = proForm->proargtypes.values[i];
 		object.objectSubId = 0;
-		appendStringInfoString(&audit_name, getObjectIdentity(&object));
+		appendStringInfoString(&audit_name, getObjectIdentity(&object, false));
 	}
 	appendStringInfoChar(&audit_name, ')');
 
@@ -164,7 +164,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_func_namespace(functionId);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -179,7 +179,7 @@ sepgsql_proc_drop(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -204,7 +204,7 @@ sepgsql_proc_relabel(Oid functionId, const char *seclabel)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_procedure:{setattr relabelfrom} permission
@@ -292,7 +292,7 @@ sepgsql_proc_setattr(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
@@ -324,7 +324,7 @@ sepgsql_proc_execute(Oid functionId)
 	object.classId = ProcedureRelationId;
 	object.objectId = functionId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_PROCEDURE,
 							SEPG_DB_PROCEDURE__EXECUTE,
diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c
index 380bc6094d..b50f386f5b 100644
--- a/contrib/sepgsql/relation.c
+++ b/contrib/sepgsql/relation.c
@@ -102,7 +102,7 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
 
 	initStringInfo(&audit_name);
 	appendStringInfo(&audit_name, "%s.%s",
-					 getObjectIdentity(&object),
+					 getObjectIdentity(&object, false),
 					 quote_identifier(NameStr(attForm->attname)));
 	sepgsql_avc_check_perms_label(ncontext,
 								  SEPG_CLASS_DB_COLUMN,
@@ -146,7 +146,7 @@ sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -178,7 +178,7 @@ sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_column:{setattr relabelfrom} permission
@@ -222,7 +222,7 @@ sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = attnum;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_COLUMN,
@@ -288,7 +288,7 @@ sepgsql_relation_post_create(Oid relOid)
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
 							SEPG_DB_SCHEMA__ADD_NAME,
-							getObjectIdentity(&object),
+							getObjectIdentity(&object, false),
 							true);
 
 	switch (classForm->relkind)
@@ -450,7 +450,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = NamespaceRelationId;
 	object.objectId = get_rel_namespace(relOid);
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -472,7 +472,7 @@ sepgsql_relation_drop(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
@@ -503,7 +503,7 @@ sepgsql_relation_drop(Oid relOid)
 			object.classId = RelationRelationId;
 			object.objectId = relOid;
 			object.objectSubId = attForm->attnum;
-			audit_name = getObjectIdentity(&object);
+			audit_name = getObjectIdentity(&object, false);
 
 			sepgsql_avc_check_perms(&object,
 									SEPG_CLASS_DB_COLUMN,
@@ -584,7 +584,7 @@ sepgsql_relation_relabel(Oid relOid, const char *seclabel)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_xxx:{setattr relabelfrom} permission
@@ -695,7 +695,7 @@ sepgsql_relation_setattr(Oid relOid)
 	object.classId = RelationRelationId;
 	object.objectId = relOid;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							tclass,
diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c
index 90ecbc1725..3b2b80be83 100644
--- a/contrib/sepgsql/schema.c
+++ b/contrib/sepgsql/schema.c
@@ -123,7 +123,7 @@ sepgsql_schema_drop(Oid namespaceId)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	sepgsql_avc_check_perms(&object,
 							SEPG_CLASS_DB_SCHEMA,
@@ -148,7 +148,7 @@ sepgsql_schema_relabel(Oid namespaceId, const char *seclabel)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	/*
 	 * check db_schema:{setattr relabelfrom} permission
@@ -186,7 +186,7 @@ check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation)
 	object.classId = NamespaceRelationId;
 	object.objectId = namespaceId;
 	object.objectSubId = 0;
-	audit_name = getObjectIdentity(&object);
+	audit_name = getObjectIdentity(&object, false);
 
 	result = sepgsql_avc_check_perms(&object,
 									 SEPG_CLASS_DB_SCHEMA,
-- 
2.27.0

#60Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#59)
Re: Cache lookup errors with functions manipulation object addresses

On 2020-Jul-06, Michael Paquier wrote:

While refreshing my mind with this code, I got surprised with the
choice of "routine" in getProcedureTypeDescription() when we need a
default object type name for an object not found, so I have switched
that to "procedure" to be more consistent.

I think "routine" was more correct. We introduced that terminology to
encompass both functions and procedures, back when real procedures were
added. See the definitions in the glossary for example.

I have also spent some time analyzing the coverage of the patch, and
did not find any obvious holes or any remaining missing_ok paths not
covered. Some comments were also a bit weird after re-reading them,
so I tweaked a couple of places.

Cool.

Attached is for now a rebased patch. If there are any comments,
please feel free. Daniel, Alvaro, does that look fine for you? I am
letting this stuff aside for a couple of days for the moment.

I'm not sure that I'll have time to review this in the next couple of
days, but it seemed sufficiently good when I last looked.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#61Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#60)
Re: Cache lookup errors with functions manipulation object addresses

On Mon, Jul 06, 2020 at 10:56:53AM -0400, Alvaro Herrera wrote:

I think "routine" was more correct. We introduced that terminology to
encompass both functions and procedures, back when real procedures were
added. See the definitions in the glossary for example.

Okay, fine by me, while we have getProcedureTypeDescription() working
on pg_proc entries ;)

I'm not sure that I'll have time to review this in the next couple of
days, but it seemed sufficiently good when I last looked.

Thanks. I'll try to come back to it next week or the one after for
another round of self-review, and I'll see what to do at this point.
This has been around for three years, it can still wait a bit.
--
Michael

#62Daniel Gustafsson
daniel@yesql.se
In reply to: Michael Paquier (#59)
Re: Cache lookup errors with functions manipulation object addresses

On 6 Jul 2020, at 09:45, Michael Paquier <michael@paquier.xyz> wrote:

Attached is for now a rebased patch. If there are any comments,
please feel free. Daniel, Alvaro, does that look fine for you? I am
letting this stuff aside for a couple of days for the moment.

Reading through I have no comments or objections, LGTM. I didn't try to apply
and run tests, but the CFBot is happy about it so I doubt I would find anything
different there.

cheers ./daniel

#63Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Michael Paquier (#61)
Re: Cache lookup errors with functions manipulation object addresses

On 2020-Jul-07, Michael Paquier wrote:

On Mon, Jul 06, 2020 at 10:56:53AM -0400, Alvaro Herrera wrote:

I think "routine" was more correct. We introduced that terminology to
encompass both functions and procedures, back when real procedures were
added. See the definitions in the glossary for example.

Okay, fine by me, while we have getProcedureTypeDescription() working
on pg_proc entries ;)

That's a holdover from old times, when we thought functions were
procedures. That's no longer the case.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#64Michael Paquier
michael@paquier.xyz
In reply to: Alvaro Herrera (#63)
Re: Cache lookup errors with functions manipulation object addresses

On Tue, Jul 07, 2020 at 11:08:30AM -0400, Alvaro Herrera wrote:
g> That's a holdover from old times, when we thought functions were

procedures. That's no longer the case.

Thanks, so "routine" it is. I have done an extra round of review of
this patch, and noticed two things:
- In getObjectDescription(), we were doing a call to get_attname() for
nothing with OCLASS_CLASS with an attribute.
- The regression test output has been changed to use \a\t to make
future diffs more readable if we add an object type that increases the
column size.

And applied the change. Thanks to everybody who took the time to look
at this code and comment about it. It took actually less than 3 years
for this threadto conclude, as it began on the 19th of July, 2017.
--
Michael