Extend ALTER DEFAULT PRIVILEGES to define default privileges for large objects.
authorFujii Masao <[email protected]>
Fri, 4 Apr 2025 10:02:17 +0000 (19:02 +0900)
committerFujii Masao <[email protected]>
Fri, 4 Apr 2025 10:02:17 +0000 (19:02 +0900)
Previously, ALTER DEFAULT PRIVILEGES did not support large objects.
This meant that to grant privileges to users other than the owner,
permissions had to be manually assigned each time a large object
was created, which was inconvenient.

This commit extends ALTER DEFAULT PRIVILEGES to allow defining default
access privileges for large objects. With this change, specified privileges
will automatically apply to newly created large objects, making privilege
management more efficient.

As a side effect, this commit introduces the new keyword OBJECTS
since it's used in the syntax of ALTER DEFAULT PRIVILEGES.

Original patch by Haruka Takatsuka, with some fixes and tests by Yugo Nagata,
and rebased by Laurenz Albe.

Author: Takatsuka Haruka <[email protected]>
Co-authored-by: Yugo Nagata <[email protected]>
Co-authored-by: Laurenz Albe <[email protected]>
Reviewed-by: Masao Fujii <[email protected]>
Discussion: https://postgr.es/m/20240424115242.236b499b2bed5b7a27f7a418@sraoss.co.jp

14 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_default_privileges.sgml
src/backend/catalog/aclchk.c
src/backend/catalog/objectaddress.c
src/backend/catalog/pg_largeobject.c
src/backend/parser/gram.y
src/bin/pg_dump/dumputils.c
src/bin/pg_dump/pg_dump.c
src/bin/psql/describe.c
src/bin/psql/tab-complete.in.c
src/include/catalog/pg_default_acl.h
src/include/parser/kwlist.h
src/test/regress/expected/privileges.out
src/test/regress/sql/privileges.sql

index 4558f940aaf49f6193930a22c38b58f5701a5404..45ba9c5118f98989fd099d507e8882575cab162c 100644 (file)
@@ -3360,7 +3360,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>S</literal> = sequence,
        <literal>f</literal> = function,
        <literal>T</literal> = type,
-       <literal>n</literal> = schema
+       <literal>n</literal> = schema,
+       <literal>L</literal> = large object
       </para></entry>
      </row>
 
index 89aacec4fab84c897242e4a6ba602071cc4689f7..6acd0f1df914c6926e6d6f5bd28b0599d66d629c 100644 (file)
@@ -51,6 +51,11 @@ GRANT { { USAGE | CREATE }
     ON SCHEMAS
     TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
 
+GRANT { { SELECT | UPDATE }
+    [, ...] | ALL [ PRIVILEGES ] }
+    ON LARGE OBJECTS
+    TO { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...] [ WITH GRANT OPTION ]
+
 REVOKE [ GRANT OPTION FOR ]
     { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER | MAINTAIN }
     [, ...] | ALL [ PRIVILEGES ] }
@@ -83,6 +88,13 @@ REVOKE [ GRANT OPTION FOR ]
     ON SCHEMAS
     FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
     [ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+    { { SELECT | UPDATE }
+    [, ...] | ALL [ PRIVILEGES ] }
+    ON LARGE OBJECTS
+    FROM { [ GROUP ] <replaceable class="parameter">role_name</replaceable> | PUBLIC } [, ...]
+    [ CASCADE | RESTRICT ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -117,8 +129,8 @@ REVOKE [ GRANT OPTION FOR ]
   <para>
    Currently,
    only the privileges for schemas, tables (including views and foreign
-   tables), sequences, functions, and types (including domains) can be
-   altered.  For this command, functions include aggregates and procedures.
+   tables), sequences, functions, types (including domains), and large objects
+   can be altered.  For this command, functions include aggregates and procedures.
    The words <literal>FUNCTIONS</literal> and <literal>ROUTINES</literal> are
    equivalent in this command.  (<literal>ROUTINES</literal> is preferred
    going forward as the standard term for functions and procedures taken
@@ -161,7 +173,8 @@ REVOKE [ GRANT OPTION FOR ]
       If <literal>IN SCHEMA</literal> is omitted, the global default privileges
       are altered.
       <literal>IN SCHEMA</literal> is not allowed when setting privileges
-      for schemas, since schemas can't be nested.
+      for schemas and large objects, since schemas can't be nested and
+      large objects don't belong to a schema.
      </para>
     </listitem>
    </varlistentry>
index 02a754cc30a70cbb991620036c80dd2a1a923e0e..9ca8a88dc91043bb4f142b70a2796798c8c1b7b2 100644 (file)
@@ -1005,6 +1005,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
            all_privileges = ACL_ALL_RIGHTS_SCHEMA;
            errormsg = gettext_noop("invalid privilege type %s for schema");
            break;
+       case OBJECT_LARGEOBJECT:
+           all_privileges = ACL_ALL_RIGHTS_LARGEOBJECT;
+           errormsg = gettext_noop("invalid privilege type %s for large object");
+           break;
        default:
            elog(ERROR, "unrecognized GrantStmt.objtype: %d",
                 (int) action->objtype);
@@ -1196,6 +1200,16 @@ SetDefaultACL(InternalDefaultACL *iacls)
                this_privileges = ACL_ALL_RIGHTS_SCHEMA;
            break;
 
+       case OBJECT_LARGEOBJECT:
+           if (OidIsValid(iacls->nspid))
+               ereport(ERROR,
+                       (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+                        errmsg("cannot use IN SCHEMA clause when using GRANT/REVOKE ON LARGE OBJECTS")));
+           objtype = DEFACLOBJ_LARGEOBJECT;
+           if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+               this_privileges = ACL_ALL_RIGHTS_LARGEOBJECT;
+           break;
+
        default:
            elog(ERROR, "unrecognized object type: %d",
                 (int) iacls->objtype);
@@ -1439,6 +1453,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
            case DEFACLOBJ_NAMESPACE:
                iacls.objtype = OBJECT_SCHEMA;
                break;
+           case DEFACLOBJ_LARGEOBJECT:
+               iacls.objtype = OBJECT_LARGEOBJECT;
+               break;
            default:
                /* Shouldn't get here */
                elog(ERROR, "unexpected default ACL type: %d",
@@ -4250,6 +4267,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid)
            defaclobjtype = DEFACLOBJ_NAMESPACE;
            break;
 
+       case OBJECT_LARGEOBJECT:
+           defaclobjtype = DEFACLOBJ_LARGEOBJECT;
+           break;
+
        default:
            return NULL;
    }
index d8eb8d3deaa9e19acaecf8918d0fe62934bfcc9f..b63fd57dc04bb57a90f995b4d34dbc55708ff174 100644 (file)
@@ -2005,16 +2005,20 @@ get_object_address_defacl(List *object, bool missing_ok)
        case DEFACLOBJ_NAMESPACE:
            objtype_str = "schemas";
            break;
+       case DEFACLOBJ_LARGEOBJECT:
+           objtype_str = "large objects";
+           break;
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                     errmsg("unrecognized default ACL object type \"%c\"", objtype),
-                    errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
+                    errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
                             DEFACLOBJ_RELATION,
                             DEFACLOBJ_SEQUENCE,
                             DEFACLOBJ_FUNCTION,
                             DEFACLOBJ_TYPE,
-                            DEFACLOBJ_NAMESPACE)));
+                            DEFACLOBJ_NAMESPACE,
+                            DEFACLOBJ_LARGEOBJECT)));
    }
 
    /*
@@ -3844,6 +3848,12 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
                                         _("default privileges on new schemas belonging to role %s"),
                                         rolename);
                        break;
+                   case DEFACLOBJ_LARGEOBJECT:
+                       Assert(!nspname);
+                       appendStringInfo(&buffer,
+                                        _("default privileges on new large objects belonging to role %s"),
+                                        rolename);
+                       break;
                    default:
                        /* shouldn't get here */
                        if (nspname)
@@ -5766,6 +5776,10 @@ getObjectIdentityParts(const ObjectAddress *object,
                        appendStringInfoString(&buffer,
                                               " on schemas");
                        break;
+                   case DEFACLOBJ_LARGEOBJECT:
+                       appendStringInfoString(&buffer,
+                                              " on large objects");
+                       break;
                }
 
                if (objname)
index 0a477a8e8a920aabe723e703f772873f7ba7cc18..71a9cc134e1af8207f010767af9677ae2508468f 100644 (file)
@@ -20,6 +20,7 @@
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_largeobject_metadata.h"
 #include "miscadmin.h"
+#include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 
@@ -39,6 +40,8 @@ LargeObjectCreate(Oid loid)
    Oid         loid_new;
    Datum       values[Natts_pg_largeobject_metadata];
    bool        nulls[Natts_pg_largeobject_metadata];
+   Oid         ownerId;
+   Acl         *lomacl;
 
    pg_lo_meta = table_open(LargeObjectMetadataRelationId,
                            RowExclusiveLock);
@@ -55,11 +58,18 @@ LargeObjectCreate(Oid loid)
        loid_new = GetNewOidWithIndex(pg_lo_meta,
                                      LargeObjectMetadataOidIndexId,
                                      Anum_pg_largeobject_metadata_oid);
+   ownerId = GetUserId();
+   lomacl = get_user_default_acl(OBJECT_LARGEOBJECT, ownerId, InvalidOid);
 
    values[Anum_pg_largeobject_metadata_oid - 1] = ObjectIdGetDatum(loid_new);
    values[Anum_pg_largeobject_metadata_lomowner - 1]
-       = ObjectIdGetDatum(GetUserId());
-   nulls[Anum_pg_largeobject_metadata_lomacl - 1] = true;
+       = ObjectIdGetDatum(ownerId);
+
+   if (lomacl != NULL)
+       values[Anum_pg_largeobject_metadata_lomacl - 1]
+           = PointerGetDatum(lomacl);
+   else
+       nulls[Anum_pg_largeobject_metadata_lomacl - 1] = true;
 
    ntup = heap_form_tuple(RelationGetDescr(pg_lo_meta),
                           values, nulls);
@@ -70,6 +80,10 @@ LargeObjectCreate(Oid loid)
 
    table_close(pg_lo_meta, RowExclusiveLock);
 
+   /* dependencies on roles mentioned in default ACL */
+   recordDependencyOnNewAcl(LargeObjectRelationId, loid_new, 0,
+                            ownerId, lomacl);
+
    return loid_new;
 }
 
index 6a094ecc54f1c98ccb7035766d44fe112cf69cc4..f1156e2fca39e42a97a40ee17111f82703b084a6 100644 (file)
@@ -752,7 +752,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
    NULLS_P NUMERIC
 
-   OBJECT_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR
+   OBJECT_P OBJECTS_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR
    ORDER ORDINALITY OTHERS OUT_P OUTER_P
    OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
@@ -8177,6 +8177,7 @@ defacl_privilege_target:
            | SEQUENCES     { $$ = OBJECT_SEQUENCE; }
            | TYPES_P       { $$ = OBJECT_TYPE; }
            | SCHEMAS       { $$ = OBJECT_SCHEMA; }
+           | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
        ;
 
 
@@ -17882,6 +17883,7 @@ unreserved_keyword:
            | NOWAIT
            | NULLS_P
            | OBJECT_P
+           | OBJECTS_P
            | OF
            | OFF
            | OIDS
@@ -18504,6 +18506,7 @@ bare_label_keyword:
            | NULLS_P
            | NUMERIC
            | OBJECT_P
+           | OBJECTS_P
            | OF
            | OFF
            | OIDS
index 5ae77f7636709a045240528dedbdaf1aba184520..ab0e9e6da3c299201e520dcc02c16e809d048796 100644 (file)
@@ -506,7 +506,8 @@ do { \
        CONVERT_PRIV('s', "SET");
        CONVERT_PRIV('A', "ALTER SYSTEM");
    }
-   else if (strcmp(type, "LARGE OBJECT") == 0)
+   else if (strcmp(type, "LARGE OBJECT") == 0 ||
+            strcmp(type, "LARGE OBJECTS") == 0)
    {
        CONVERT_PRIV('r', "SELECT");
        CONVERT_PRIV('w', "UPDATE");
index eef74f78271091da0511603ad35c0c88e4da1ed6..9436b25290d403b6cd5520572a48bf91a0df846e 100644 (file)
@@ -15679,6 +15679,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
        case DEFACLOBJ_NAMESPACE:
            type = "SCHEMAS";
            break;
+       case DEFACLOBJ_LARGEOBJECT:
+           type = "LARGE OBJECTS";
+           break;
        default:
            /* shouldn't get here */
            pg_fatal("unrecognized object type in default privileges: %d",
index e038e9dc9e27b2531ebb796a42ec80e329b7b5f7..8970677ac64b65a9b6fdda33eb90bb3cb35d037a 100644 (file)
@@ -1222,7 +1222,9 @@ listDefaultACLs(const char *pattern)
    printfPQExpBuffer(&buf,
                      "SELECT pg_catalog.pg_get_userbyid(d.defaclrole) AS \"%s\",\n"
                      "  n.nspname AS \"%s\",\n"
-                     "  CASE d.defaclobjtype WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
+                     "  CASE d.defaclobjtype "
+                     "    WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s'"
+                     "    WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' WHEN '%c' THEN '%s' END AS \"%s\",\n"
                      "  ",
                      gettext_noop("Owner"),
                      gettext_noop("Schema"),
@@ -1236,6 +1238,8 @@ listDefaultACLs(const char *pattern)
                      gettext_noop("type"),
                      DEFACLOBJ_NAMESPACE,
                      gettext_noop("schema"),
+                     DEFACLOBJ_LARGEOBJECT,
+                     gettext_noop("large object"),
                      gettext_noop("Type"));
 
    printACLColumn(&buf, "d.defaclacl");
index 98951aef82ca3da3628d80178486816400a5312a..c916b9299a80e94c713832fe1bf84466f2314ed6 100644 (file)
@@ -4457,7 +4457,7 @@ match_previous_words(int pattern_id,
         * objects supported.
         */
        if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
-           COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
+           COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS", "LARGE OBJECTS");
        else
            COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_grantables,
                                            "ALL FUNCTIONS IN SCHEMA",
index 728024b1fa74732a30766e64ade7bcb8f33a8d33..ce6e5098eaf02d8c3d72dbbba9191f54bc54312b 100644 (file)
@@ -68,6 +68,7 @@ MAKE_SYSCACHE(DEFACLROLENSPOBJ, pg_default_acl_role_nsp_obj_index, 8);
 #define DEFACLOBJ_FUNCTION     'f' /* function */
 #define DEFACLOBJ_TYPE         'T' /* type */
 #define DEFACLOBJ_NAMESPACE        'n' /* namespace */
+#define DEFACLOBJ_LARGEOBJECT  'L' /* large object */
 
 #endif                         /* EXPOSE_TO_CLIENT_CODE */
 
index 40cf090ce61f8a5917dc7f4acbc7c8e5b72400d9..a4af3f717a1118e4b3561786c9f642c2ca5772d5 100644 (file)
@@ -308,6 +308,7 @@ PG_KEYWORD("nullif", NULLIF, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("nulls", NULLS_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("numeric", NUMERIC, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("object", OBJECT_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("objects", OBJECTS_P, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("of", OF, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL)
index 5588d83e1bfb5aff3b61e3881bb6df6db4c241d7..1fddb13b6aef2b4663daa5e6b7838c10f3ac789b 100644 (file)
@@ -2667,11 +2667,103 @@ SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'CREATE'); -- yes
 
 ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2;
 COMMIT;
+--
+-- Test for default privileges on large objects. This is done in a
+-- separate, rollbacked, transaction to avoid any trouble with other
+-- regression sessions.
+--
+BEGIN;
+SELECT lo_create(1007);
+ lo_create 
+-----------
+      1007
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1007, 'SELECT'); -- no
+ has_largeobject_privilege 
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1007, 'UPDATE'); -- no
+ has_largeobject_privilege 
+---------------------------
+ f
+(1 row)
+
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_create(1008);
+ lo_create 
+-----------
+      1008
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1008, 'SELECT'); -- yes
+ has_largeobject_privilege 
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user6', 1008, 'SELECT'); -- no
+ has_largeobject_privilege 
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1008, 'UPDATE'); -- no
+ has_largeobject_privilege 
+---------------------------
+ f
+(1 row)
+
+ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_create(1009);
+ lo_create 
+-----------
+      1009
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1009, 'SELECT'); -- true
+ has_largeobject_privilege 
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1009, 'UPDATE'); -- true
+ has_largeobject_privilege 
+---------------------------
+ t
+(1 row)
+
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_create(1010);
+ lo_create 
+-----------
+      1010
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1010, 'SELECT'); -- true
+ has_largeobject_privilege 
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user2', 1010, 'UPDATE'); -- false
+ has_largeobject_privilege 
+---------------------------
+ f
+(1 row)
+
+ROLLBACK;
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON LARGE OBJECTS TO public; -- error
+ERROR:  cannot use IN SCHEMA clause when using GRANT/REVOKE ON LARGE OBJECTS
+\c -
 -- Test for DROP OWNED BY with shared dependencies.  This is done in a
 -- separate, rollbacked, transaction to avoid any trouble with other
 -- regression sessions.
 BEGIN;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2;
+ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2;
@@ -2682,7 +2774,7 @@ SELECT count(*) FROM pg_shdepend
    classid = 'pg_default_acl'::regclass;
  count 
 -------
-     5
+     6
 (1 row)
 
 DROP OWNED BY regress_priv_user2, regress_priv_user2;
index 286b1d037569250762108ea63619c5379cededc9..85d7280f35fca1c8a31e3e488b1ff3d2886bb7b2 100644 (file)
@@ -1586,11 +1586,47 @@ ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2;
 
 COMMIT;
 
+--
+-- Test for default privileges on large objects. This is done in a
+-- separate, rollbacked, transaction to avoid any trouble with other
+-- regression sessions.
+--
+
+BEGIN;
+
+SELECT lo_create(1007);
+SELECT has_largeobject_privilege('regress_priv_user2', 1007, 'SELECT'); -- no
+SELECT has_largeobject_privilege('regress_priv_user2', 1007, 'UPDATE'); -- no
+
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO regress_priv_user2;
+
+SELECT lo_create(1008);
+SELECT has_largeobject_privilege('regress_priv_user2', 1008, 'SELECT'); -- yes
+SELECT has_largeobject_privilege('regress_priv_user6', 1008, 'SELECT'); -- no
+SELECT has_largeobject_privilege('regress_priv_user2', 1008, 'UPDATE'); -- no
+
+ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_create(1009);
+SELECT has_largeobject_privilege('regress_priv_user2', 1009, 'SELECT'); -- true
+SELECT has_largeobject_privilege('regress_priv_user2', 1009, 'UPDATE'); -- true
+
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_create(1010);
+SELECT has_largeobject_privilege('regress_priv_user2', 1010, 'SELECT'); -- true
+SELECT has_largeobject_privilege('regress_priv_user2', 1010, 'UPDATE'); -- false
+
+ROLLBACK;
+
+ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON LARGE OBJECTS TO public; -- error
+
+\c -
+
 -- Test for DROP OWNED BY with shared dependencies.  This is done in a
 -- separate, rollbacked, transaction to avoid any trouble with other
 -- regression sessions.
 BEGIN;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2;
+ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2;
 ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2;