Add has_large_object_privilege function
Hi,
Currently, there are many has_*_privilege functions for table, column,
function, type, role, database, schema, language, server, foreign data
wrapper, parameter, and so on. However, large object is not supported yet.
I can find a way to check the privilege on a large object in the regression
test, in which whether a function call such as lo_open(lowrite(..)) raises
an error or not is checked. However, I think it is not good that we need to
try to write to a large object to check we can write it, and also the
transaction will be aborted due to a permission error when the user doesn't
have the privilege. So, I would like to propose to add
has_large_object_function for checking if a user has the privilege on a large
object.
I attached two files of patches.
0001 makes a bit refactoring on large object codes. To check if a large
object exists, myLargeObjectExists() function has to be used rather than
public LargeObjectExists(), because we need to use different snapshots between
read and write cases to make the behavior compatible to lo_open. However,
myLargeObjectExists() was static function, so I made it public and renamed it
to LargeObjectExistsWIthSnapshot(). Also, since these two functions are almost
same except to whether snapshot can be specified, I rewrote LargeObjectExists to
call LargeObjectExistsWIthSnapshot internally. I am not sure why these
duplicated codes have been left for long time, and there might be some reasons.
However, otherwise, I think this deduplication also could reduce possible
maintenance cost in future.
0002 adds has_large_object_privilege function.There are three variations whose
arguments are combinations of large object OID with user name, user OID, or
implicit user (current_user). It returns NULL if not-existing large object id is
specified, and false if non-existing user id is specified, and raises an error if
non-existing user name is specified. These behavior is similar with has_table_privilege.
The regression test is also included.
Regards,
Yugo Nagata
--
Yugo NAGATA <nagata@sraoss.co.jp>
Attachments:
0002-Add-has_large_object_privilege-function.patchtext/x-diff; name=0002-Add-has_large_object_privilege-function.patchDownload
From af348b3629be07dd73fca5920f91b7309bc9d3b6 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 2 Jul 2024 15:12:17 +0900
Subject: [PATCH 2/2] Add has_large_object_privilege function
This function is for checking whether a user has the privilege on a
large object. There are three variations whose arguments are combinations
of large object OID with user name, user OID, or implicit user (current_user).
It returns NULL if not-existing large object id is specified, and false if
non-existing user id is specified, and raises an error if non-existing user
name is specified. These behavior is similar with has_table_privilege.
---
doc/src/sgml/func.sgml | 18 +++
src/backend/utils/adt/acl.c | 140 +++++++++++++++++++
src/include/catalog/pg_proc.dat | 13 ++
src/test/regress/expected/privileges.out | 169 +++++++++++++++++++++++
src/test/regress/sql/privileges.sql | 44 ++++++
5 files changed, 384 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f1f22a1960..e06135ca9a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24975,6 +24975,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>has_large_object_privilege</primary>
+ </indexterm>
+ <function>has_large_object_privilege</function> (
+ <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+ <parameter>largeobject</parameter> <type>oid</type>,
+ <parameter>privilege</parameter> <type>text</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does user have privilege for large object?
+ Allowable privilege types are
+ <literal>SELECT</literal> and <literal>UPDATE</literal>.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index d7b39140b3..87f2b6c212 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -26,6 +26,7 @@
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
+#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
@@ -39,6 +40,7 @@
#include "lib/bloomfilter.h"
#include "lib/qunique.h"
#include "miscadmin.h"
+#include "storage/large_object.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -46,6 +48,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
@@ -124,6 +127,7 @@ static AclMode convert_tablespace_priv_string(text *priv_type_text);
static Oid convert_type_name(text *typename);
static AclMode convert_type_priv_string(text *priv_type_text);
static AclMode convert_parameter_priv_string(text *priv_text);
+static AclMode convert_large_object_priv_string(text *priv_text);
static AclMode convert_role_priv_string(text *priv_type_text);
static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
@@ -4669,6 +4673,142 @@ convert_parameter_priv_string(text *priv_text)
return convert_any_priv_string(priv_text, parameter_priv_map);
}
+/*
+ * has_large_objec_privilege variants
+ * These are all named "has_large_object_privilege" at the SQL level.
+ * They take various combinations of large object OID with
+ * user name, user OID, or implicit user = current_user.
+ *
+ * The result is a boolean value: true if user has been granted
+ * the indicated privilege or false if not.
+ */
+
+/*
+ * has_large_object_privilege_name_id
+ * Check user privileges on a large object given
+ * name username, large object oid, and text priv name.
+ */
+Datum
+has_large_object_privilege_name_id(PG_FUNCTION_ARGS)
+{
+ Name username = PG_GETARG_NAME(0);
+ Oid roleid = get_role_oid_or_public(NameStr(*username));
+ Oid lobjId = PG_GETARG_OID(1);
+ text *priv_type_text = PG_GETARG_TEXT_PP(2);
+ AclMode mode;
+ AclResult aclresult;
+ Snapshot snapshot = NULL;
+
+ mode = convert_large_object_priv_string(priv_type_text);
+
+ if (mode & ACL_UPDATE)
+ snapshot = NULL;
+ else
+ snapshot = GetActiveSnapshot();
+
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
+ PG_RETURN_NULL();
+
+ if (lo_compat_privileges)
+ PG_RETURN_BOOL(true);
+
+ aclresult = pg_largeobject_aclcheck_snapshot(lobjId,
+ roleid,
+ mode,
+ snapshot);
+
+ PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+okui chiba * has_large_object_privilege_id
+ * Check user privileges on a large object given
+ * large object oid, and text priv name.
+ * current_user is assumed
+ */
+Datum
+has_large_object_privilege_id(PG_FUNCTION_ARGS)
+{
+ Oid lobjId = PG_GETARG_OID(0);
+ Oid roleid = GetUserId();
+ text *priv_type_text = PG_GETARG_TEXT_PP(1);
+ AclMode mode;
+ AclResult aclresult;
+ Snapshot snapshot = NULL;
+
+ mode = convert_large_object_priv_string(priv_type_text);
+
+ if (mode & ACL_UPDATE)
+ snapshot = NULL;
+ else
+ snapshot = GetActiveSnapshot();
+
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
+ PG_RETURN_NULL();
+
+ if (lo_compat_privileges)
+ PG_RETURN_BOOL(true);
+
+ aclresult = pg_largeobject_aclcheck_snapshot(lobjId,
+ roleid,
+ mode,
+ snapshot);
+
+ PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_large_object_privilege_id_id
+ * Check user privileges on a large object given
+ * roleid, large object oid, and text priv name.
+ */
+Datum
+has_large_object_privilege_id_id(PG_FUNCTION_ARGS)
+{
+ Oid roleid = PG_GETARG_OID(0);
+ Oid lobjId = PG_GETARG_OID(1);
+ text *priv_type_text = PG_GETARG_TEXT_PP(2);
+ AclMode mode;
+ AclResult aclresult;
+ Snapshot snapshot = NULL;
+
+ mode = convert_large_object_priv_string(priv_type_text);
+
+ if (mode & ACL_UPDATE)
+ snapshot = NULL;
+ else
+ snapshot = GetActiveSnapshot();
+
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
+ PG_RETURN_NULL();
+
+ if (lo_compat_privileges)
+ PG_RETURN_BOOL(true);
+
+ aclresult = pg_largeobject_aclcheck_snapshot(lobjId,
+ roleid,
+ mode,
+ snapshot);
+
+ PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * convert_large_object_priv_string
+ * Convert text string to AclMode value.
+ */
+static AclMode
+convert_large_object_priv_string(text *priv_text)
+{
+ static const priv_map parameter_priv_map[] = {
+ {"SELECT", ACL_SELECT},
+ {"UPDATE", ACL_UPDATE},
+ {NULL, 0}
+ };
+
+ return convert_any_priv_string(priv_text, parameter_priv_map);
+}
+
/*
* pg_has_role variants
* These are all named "pg_has_role" at the SQL level.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d4ac578ae6..5b0ef8df68 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5300,6 +5300,19 @@
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_any_column_privilege_id' },
+{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
+ proname => 'has_large_object_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'name oid text',
+ prosrc => 'has_large_object_privilege_name_id' },
+{ oid => '4552', descr => 'current privilege on large objct by large object oid',
+ proname => 'has_large_object_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid text',
+ prosrc => 'has_large_object_privilege_id' },
+{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
+ proname => 'has_large_object_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid oid text',
+ prosrc => 'has_large_object_privilege_id_id' },
+
{ oid => '3355', descr => 'I/O',
proname => 'pg_ndistinct_in', prorettype => 'pg_ndistinct',
proargtypes => 'cstring', prosrc => 'pg_ndistinct_in' },
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index eb4b762ea1..7933601590 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2024,10 +2024,167 @@ SELECT lo_truncate(lo_open(2001, x'20000'::int), 10);
0
(1 row)
+-- has_large_object_privilege function
+-- superuser
+\c -
+SELECT has_large_object_privilege(1001, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1002, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1003, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1004, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1001, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1002, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1003, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1004, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+-- not-existing large object
+SELECT has_large_object_privilege(9999, 'SELECT'); -- NULL
+ has_large_object_privilege
+----------------------------
+
+(1 row)
+
+-- not-existing user
+SELECT has_large_object_privilege(-99999, 1001, 'SELECT'); -- false
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+-- non-superuser
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT has_large_object_privilege(1001, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1002, 'SELECT'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
+SELECT has_large_object_privilege(1003, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1004, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1001, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1002, 'UPDATE'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
+SELECT has_large_object_privilege(1003, 'UPDATE'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
+SELECT has_large_object_privilege(1004, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege('regress_priv_user3', 1001, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege('regress_priv_user3', 1003, 'SELECT'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
+SELECT has_large_object_privilege('regress_priv_user3', 1005, 'SELECT');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
+SELECT has_large_object_privilege('regress_priv_user3', 2001, 'UPDATE');
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
-- compatibility mode in largeobject permission
\c -
SET lo_compat_privileges = false; -- default setting
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_large_object_privilege(1002, 'SELECT'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
+SELECT has_large_object_privilege(1002, 'UPDATE'); -- false
+ has_large_object_privilege
+----------------------------
+ f
+(1 row)
+
SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied
ERROR: permission denied for large object 1002
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied
@@ -2047,6 +2204,18 @@ ERROR: permission denied for function lo_import
\c -
SET lo_compat_privileges = true; -- compatibility mode
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_large_object_privilege(1002, 'SELECT'); -- true
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
+SELECT has_large_object_privilege(1002, 'UPDATE'); -- true
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
+
SELECT loread(lo_open(1002, x'40000'::int), 32);
loread
--------
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index eeb4c00292..6b509e993a 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1318,11 +1318,52 @@ SELECT loread(lo_open(1005, x'40000'::int), 32);
SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied
SELECT lo_truncate(lo_open(2001, x'20000'::int), 10);
+-- has_large_object_privilege function
+
+-- superuser
+\c -
+SELECT has_large_object_privilege(1001, 'SELECT');
+SELECT has_large_object_privilege(1002, 'SELECT');
+SELECT has_large_object_privilege(1003, 'SELECT');
+SELECT has_large_object_privilege(1004, 'SELECT');
+
+SELECT has_large_object_privilege(1001, 'UPDATE');
+SELECT has_large_object_privilege(1002, 'UPDATE');
+SELECT has_large_object_privilege(1003, 'UPDATE');
+SELECT has_large_object_privilege(1004, 'UPDATE');
+
+-- not-existing large object
+SELECT has_large_object_privilege(9999, 'SELECT'); -- NULL
+-- not-existing user
+SELECT has_large_object_privilege(-99999, 1001, 'SELECT'); -- false
+
+-- non-superuser
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT has_large_object_privilege(1001, 'SELECT');
+SELECT has_large_object_privilege(1002, 'SELECT'); -- false
+SELECT has_large_object_privilege(1003, 'SELECT');
+SELECT has_large_object_privilege(1004, 'SELECT');
+
+SELECT has_large_object_privilege(1001, 'UPDATE');
+SELECT has_large_object_privilege(1002, 'UPDATE'); -- false
+SELECT has_large_object_privilege(1003, 'UPDATE'); -- false
+SELECT has_large_object_privilege(1004, 'UPDATE');
+
+SELECT has_large_object_privilege('regress_priv_user3', 1001, 'SELECT');
+SELECT has_large_object_privilege('regress_priv_user3', 1003, 'SELECT'); -- false
+SELECT has_large_object_privilege('regress_priv_user3', 1005, 'SELECT');
+
+SELECT has_large_object_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false
+SELECT has_large_object_privilege('regress_priv_user3', 2001, 'UPDATE');
+
-- compatibility mode in largeobject permission
\c -
SET lo_compat_privileges = false; -- default setting
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_large_object_privilege(1002, 'SELECT'); -- false
+SELECT has_large_object_privilege(1002, 'UPDATE'); -- false
+
SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied
SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied
@@ -1336,6 +1377,9 @@ SELECT lo_import('/dev/null', 2003); -- to be denied
SET lo_compat_privileges = true; -- compatibility mode
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_large_object_privilege(1002, 'SELECT'); -- true
+SELECT has_large_object_privilege(1002, 'UPDATE'); -- true
+
SELECT loread(lo_open(1002, x'40000'::int), 32);
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd');
SELECT lo_truncate(lo_open(1002, x'20000'::int), 10);
--
2.25.1
0001-Deduplicate-codes-of-LargeObjectExists-and-mvLargeOb.patchtext/x-diff; name=0001-Deduplicate-codes-of-LargeObjectExists-and-mvLargeOb.patchDownload
From 1534f56595ca92fb156f8cb61c1644161aa56fe1 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 2 Jul 2024 15:12:09 +0900
Subject: [PATCH 1/2] Deduplicate codes of LargeObjectExists and
mvLargeObjectExists
myLargeObjectExists() and LargeObjectExists() share almost same codes
except to whether snapshot can be specified. To deduplicate it,
thie commit renames myLargeObjectExists() to LargeObjectExistsWithSnapshot()
and rewrites LargeObjectExists() to call this internally.
---
src/backend/catalog/pg_largeobject.c | 11 +++++-
src/backend/storage/large_object/inv_api.c | 40 +---------------------
src/include/catalog/pg_largeobject.h | 2 ++
3 files changed, 13 insertions(+), 40 deletions(-)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..89ca274697 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -153,6 +153,15 @@ LargeObjectDrop(Oid loid)
*/
bool
LargeObjectExists(Oid loid)
+{
+ return LargeObjectExistsWithSnapshot(loid, NULL);
+}
+
+/*
+ * Same as LargeObjectExists(), except snapshot to read with can be specified.
+ */
+bool
+LargeObjectExistsWithSnapshot(Oid loid, Snapshot snapshot)
{
Relation pg_lo_meta;
ScanKeyData skey[1];
@@ -170,7 +179,7 @@ LargeObjectExists(Oid loid)
sd = systable_beginscan(pg_lo_meta,
LargeObjectMetadataOidIndexId, true,
- NULL, 1, skey);
+ snapshot, 1, skey);
tuple = systable_getnext(sd);
if (HeapTupleIsValid(tuple))
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index f951083324..afce51c167 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -41,7 +41,6 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_largeobject.h"
-#include "catalog/pg_largeobject_metadata.h"
#include "libpq/libpq-fs.h"
#include "miscadmin.h"
#include "storage/large_object.h"
@@ -123,43 +122,6 @@ close_lo_relation(bool isCommit)
}
-/*
- * Same as pg_largeobject.c's LargeObjectExists(), except snapshot to
- * read with can be specified.
- */
-static bool
-myLargeObjectExists(Oid loid, Snapshot snapshot)
-{
- Relation pg_lo_meta;
- ScanKeyData skey[1];
- SysScanDesc sd;
- HeapTuple tuple;
- bool retval = false;
-
- ScanKeyInit(&skey[0],
- Anum_pg_largeobject_metadata_oid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(loid));
-
- pg_lo_meta = table_open(LargeObjectMetadataRelationId,
- AccessShareLock);
-
- sd = systable_beginscan(pg_lo_meta,
- LargeObjectMetadataOidIndexId, true,
- snapshot, 1, skey);
-
- tuple = systable_getnext(sd);
- if (HeapTupleIsValid(tuple))
- retval = true;
-
- systable_endscan(sd);
-
- table_close(pg_lo_meta, AccessShareLock);
-
- return retval;
-}
-
-
/*
* Extract data field from a pg_largeobject tuple, detoasting if needed
* and verifying that the length is sane. Returns data pointer (a bytea *),
@@ -279,7 +241,7 @@ inv_open(Oid lobjId, int flags, MemoryContext mcxt)
snapshot = GetActiveSnapshot();
/* Can't use LargeObjectExists here because we need to specify snapshot */
- if (!myLargeObjectExists(lobjId, snapshot))
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("large object %u does not exist", lobjId)));
diff --git a/src/include/catalog/pg_largeobject.h b/src/include/catalog/pg_largeobject.h
index b40c90b749..e684c58ca9 100644
--- a/src/include/catalog/pg_largeobject.h
+++ b/src/include/catalog/pg_largeobject.h
@@ -20,6 +20,7 @@
#include "catalog/genbki.h"
#include "catalog/pg_largeobject_d.h"
+#include "utils/snapshot.h"
/* ----------------
* pg_largeobject definition. cpp turns this into
@@ -49,5 +50,6 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_largeobject_loid_pn_index, 2683, LargeObjectLOidPNI
extern Oid LargeObjectCreate(Oid loid);
extern void LargeObjectDrop(Oid loid);
extern bool LargeObjectExists(Oid loid);
+extern bool LargeObjectExistsWithSnapshot(Oid loid, Snapshot snapshot);
#endif /* PG_LARGEOBJECT_H */
--
2.25.1
On 2024/07/02 16:34, Yugo NAGATA wrote:
So, I would like to propose to add
has_large_object_function for checking if a user has the privilege on a large
object.
+1
BTW since we already have pg_largeobject, using has_largeobject_privilege might
offer better consistency. However, I'm okay with the current name for now.
Even after committing the patch, we can rename it if others prefer has_largeobject_privilege.
I am not sure why these
duplicated codes have been left for long time, and there might be some reasons.
However, otherwise, I think this deduplication also could reduce possible
maintenance cost in future.
I couldn't find the discussion that mentioned that reason either,
but I agree with simplifying the code.
As for 0001.patch, should we also remove the inclusion of "access/genam.h" and
"access/htup_details.h" since they're no longer needed?
0002 adds has_large_object_privilege function.There are three variations whose
arguments are combinations of large object OID with user name, user OID, or
implicit user (current_user).
As for 0002.patch, as the code in these three functions is mostly the same,
it might be more efficient to create a common internal function and have
the three functions call it for simplicity.
Here are other review comments for 0002.patch.
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>has_large_object_privilege</primary>
In the documentation, access privilege inquiry functions are listed
alphabetically. So, this function's description should come right
after has_language_privilege.
+ * has_large_objec_privilege variants
Typo: s/objec/object
+ * The result is a boolean value: true if user has been granted
+ * the indicated privilege or false if not.
The comment should clarify that NULL is returned if the specified
large object doesn’t exist. For example,
--------------
The result is a boolean value: true if user has the indicated
privilege, false if not, or NULL if object doesn't exist.
--------------
+convert_large_object_priv_string(text *priv_text)
It would be better to use "priv_type_text" instead of "priv_text"
for consistency with similar functions.
+ static const priv_map parameter_priv_map[] = {
+ {"SELECT", ACL_SELECT},
+ {"UPDATE", ACL_UPDATE},
parameter_priv_map should be large_object_priv_map.
Additionally, the entries for "WITH GRANT OPTION" should be included here.
+-- not-existing user
+SELECT has_large_object_privilege(-99999, 1001, 'SELECT'); -- false
+ has_large_object_privilege
+----------------------------
+ t
+(1 row)
The comment states the result should be false, but the actual result is true.
One of them seems incorrect.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Mon, 9 Sep 2024 22:59:34 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2024/07/02 16:34, Yugo NAGATA wrote:
So, I would like to propose to add
has_large_object_function for checking if a user has the privilege on a large
object.+1
Thank you for your looking into this!
I've attached a updated patch.
BTW since we already have pg_largeobject, using has_largeobject_privilege might
offer better consistency. However, I'm okay with the current name for now.
Even after committing the patch, we can rename it if others prefer has_largeobject_privilege.
I was also wandering which is better, so I renamed it because it seems at least one person,
you, have an idea that has_largeobject_privilege might be better. If it is found that majority
prefer the previous name, I'll get it back.
As for 0001.patch, should we also remove the inclusion of "access/genam.h" and
"access/htup_details.h" since they're no longer needed?
Removed.
0002 adds has_large_object_privilege function.There are three variations whose
arguments are combinations of large object OID with user name, user OID, or
implicit user (current_user).As for 0002.patch, as the code in these three functions is mostly the same,
it might be more efficient to create a common internal function and have
the three functions call it for simplicity.
I made a new internal function "has_lo_priv_byid" that is called from these
functions.
Here are other review comments for 0002.patch.
+ <row> + <entry role="func_table_entry"><para role="func_signature"> + <indexterm> + <primary>has_large_object_privilege</primary>In the documentation, access privilege inquiry functions are listed
alphabetically. So, this function's description should come right
after has_language_privilege.
Fixed.
+ * has_large_objec_privilege variants
Typo: s/objec/object
Fixed.
+ * The result is a boolean value: true if user has been granted + * the indicated privilege or false if not.The comment should clarify that NULL is returned if the specified
large object doesn’t exist. For example,
--------------
The result is a boolean value: true if user has the indicated
privilege, false if not, or NULL if object doesn't exist.
--------------
Fixed.
+convert_large_object_priv_string(text *priv_text)
It would be better to use "priv_type_text" instead of "priv_text"
for consistency with similar functions.+ static const priv_map parameter_priv_map[] = { + {"SELECT", ACL_SELECT}, + {"UPDATE", ACL_UPDATE},parameter_priv_map should be large_object_priv_map.
Fixed.
Additionally, the entries for "WITH GRANT OPTION" should be included here.
Fixed.
+-- not-existing user +SELECT has_large_object_privilege(-99999, 1001, 'SELECT'); -- false + has_large_object_privilege +---------------------------- + t +(1 row)The comment states the result should be false, but the actual result is true.
One of them seems incorrect.
I misunderstood that has_table_privilege always returns false for not-existing user,
but it was not correct. Actually, it returns true if the privilege is granted to public.
I removed this check from the test at last because I don't think it is important.
Regards,
Yugo Nagata
--
Yugo NAGATA <nagata@sraoss.co.jp>
Attachments:
v2-0002-Add-has_largeobject_privilege-function.patchtext/x-diff; name=v2-0002-Add-has_largeobject_privilege-function.patchDownload
From 98c2ce7ef1f03aac7a3bbfdc8c1599ea92d253f8 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 2 Jul 2024 15:12:17 +0900
Subject: [PATCH v2 2/2] Add has_largeobject_privilege function
This function is for checking whether a user has the privilege on a
large object. There are three variations whose arguments are combinations
of large object OID with user name, user OID, or implicit user (current_user).
It returns NULL if not-existing large object id is specified, and raises an
error if non-existing user name is specified. These behavior is similar
with has_table_privilege.
---
doc/src/sgml/func.sgml | 18 +++
src/backend/utils/adt/acl.c | 140 ++++++++++++++++++++
src/include/catalog/pg_proc.dat | 13 ++
src/test/regress/expected/privileges.out | 162 +++++++++++++++++++++++
src/test/regress/sql/privileges.sql | 42 ++++++
5 files changed, 375 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 461fc3f437..57f2e1f29b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25113,6 +25113,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>has_largeobject_privilege</primary>
+ </indexterm>
+ <function>has_largeobject_privilege</function> (
+ <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+ <parameter>largeobject</parameter> <type>oid</type>,
+ <parameter>privilege</parameter> <type>text</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does user have privilege for large object?
+ Allowable privilege types are
+ <literal>SELECT</literal> and <literal>UPDATE</literal>.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index d7b39140b3..775b900712 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -26,6 +26,7 @@
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
+#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
@@ -39,6 +40,7 @@
#include "lib/bloomfilter.h"
#include "lib/qunique.h"
#include "miscadmin.h"
+#include "storage/large_object.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -46,6 +48,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
@@ -124,6 +127,7 @@ static AclMode convert_tablespace_priv_string(text *priv_type_text);
static Oid convert_type_name(text *typename);
static AclMode convert_type_priv_string(text *priv_type_text);
static AclMode convert_parameter_priv_string(text *priv_text);
+static AclMode convert_largeobject_priv_string(text *priv_text);
static AclMode convert_role_priv_string(text *priv_type_text);
static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
@@ -4669,6 +4673,142 @@ convert_parameter_priv_string(text *priv_text)
return convert_any_priv_string(priv_text, parameter_priv_map);
}
+/*
+ * has_largeobject_privilege variants
+ * These are all named "has_largeobject_privilege" at the SQL level.
+ * They take various combinations of large object OID with
+ * user name, user OID, or implicit user = current_user.
+ *
+ * The result is a boolean value: true if user has the indicated
+ * privilege, false if not, or NULL if object doesn't exist.
+ */
+
+/*
+ * has_lo_priv_byid
+ *
+ * Helper function to check user privileges on a large object given the
+ * role by Oid, large object by id, and privileges as AclMode.
+ */
+static bool
+has_lo_priv_byid(Oid roleid, Oid lobjId, AclMode priv, bool *is_missing)
+{
+ Snapshot snapshot = NULL;
+ AclResult aclresult;
+
+ if (priv & ACL_UPDATE)
+ snapshot = NULL;
+ else
+ snapshot = GetActiveSnapshot();
+
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
+ {
+ Assert(is_missing != NULL);
+ *is_missing = true;
+ return false;
+ }
+
+ if (lo_compat_privileges)
+ return true;
+
+ aclresult = pg_largeobject_aclcheck_snapshot(lobjId,
+ roleid,
+ priv,
+ snapshot);
+ return aclresult == ACLCHECK_OK;
+}
+
+/*
+ * has_largeobject_privilege_name_id
+ * Check user privileges on a large object given
+ * name username, large object oid, and text priv name.
+ */
+Datum
+has_largeobject_privilege_name_id(PG_FUNCTION_ARGS)
+{
+ Name username = PG_GETARG_NAME(0);
+ Oid roleid = get_role_oid_or_public(NameStr(*username));
+ Oid lobjId = PG_GETARG_OID(1);
+ text *priv_type_text = PG_GETARG_TEXT_PP(2);
+ AclMode mode;
+ bool is_missing = false;
+ bool result;
+
+ mode = convert_largeobject_priv_string(priv_type_text);
+ result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing);
+
+ if (is_missing)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+okui chiba * has_largeobject_privilege_id
+ * Check user privileges on a large object given
+ * large object oid, and text priv name.
+ * current_user is assumed
+ */
+Datum
+has_largeobject_privilege_id(PG_FUNCTION_ARGS)
+{
+ Oid lobjId = PG_GETARG_OID(0);
+ Oid roleid = GetUserId();
+ text *priv_type_text = PG_GETARG_TEXT_PP(1);
+ AclMode mode;
+ bool is_missing = false;
+ bool result;
+
+ mode = convert_largeobject_priv_string(priv_type_text);
+ result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing);
+
+ if (is_missing)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * has_largeobject_privilege_id_id
+ * Check user privileges on a large object given
+ * roleid, large object oid, and text priv name.
+ */
+Datum
+has_largeobject_privilege_id_id(PG_FUNCTION_ARGS)
+{
+ Oid roleid = PG_GETARG_OID(0);
+ Oid lobjId = PG_GETARG_OID(1);
+ text *priv_type_text = PG_GETARG_TEXT_PP(2);
+ AclMode mode;
+ bool is_missing = false;
+ bool result;
+
+ mode = convert_largeobject_priv_string(priv_type_text);
+ result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing);
+
+ if (is_missing)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * convert_largeobject_priv_string
+ * Convert text string to AclMode value.
+ */
+static AclMode
+convert_largeobject_priv_string(text *priv_type_text)
+{
+ static const priv_map largeobject_priv_map[] = {
+ {"SELECT", ACL_SELECT},
+ {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
+ {"UPDATE", ACL_UPDATE},
+ {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
+ {NULL, 0}
+ };
+
+ return convert_any_priv_string(priv_type_text, largeobject_priv_map);
+}
+
/*
* pg_has_role variants
* These are all named "pg_has_role" at the SQL level.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ff5436acac..d1da9a8947 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5333,6 +5333,19 @@
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_any_column_privilege_id' },
+{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
+ proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'name oid text',
+ prosrc => 'has_largeobject_privilege_name_id' },
+{ oid => '4552', descr => 'current privilege on large objct by large object oid',
+ proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid text',
+ prosrc => 'has_largeobject_privilege_id' },
+{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
+ proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid oid text',
+ prosrc => 'has_largeobject_privilege_id_id' },
+
{ oid => '3355', descr => 'I/O',
proname => 'pg_ndistinct_in', prorettype => 'pg_ndistinct',
proargtypes => 'cstring', prosrc => 'pg_ndistinct_in' },
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index fab0cc800f..f0506951da 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2077,10 +2077,160 @@ SELECT lo_truncate(lo_open(2001, x'20000'::int), 10);
0
(1 row)
+-- has_largeobject_privilege function
+-- superuser
+\c -
+SELECT has_largeobject_privilege(1001, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+-- not-existing large object
+SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL
+ has_largeobject_privilege
+---------------------------
+
+(1 row)
+
+-- non-superuser
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT has_largeobject_privilege(1001, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
-- compatibility mode in largeobject permission
\c -
SET lo_compat_privileges = false; -- default setting
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied
ERROR: permission denied for large object 1002
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied
@@ -2100,6 +2250,18 @@ ERROR: permission denied for function lo_import
\c -
SET lo_compat_privileges = true; -- compatibility mode
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- true
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
SELECT loread(lo_open(1002, x'40000'::int), 32);
loread
--------
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index ae338e8cc8..b8101ea6fa 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1328,11 +1328,50 @@ SELECT loread(lo_open(1005, x'40000'::int), 32);
SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied
SELECT lo_truncate(lo_open(2001, x'20000'::int), 10);
+-- has_largeobject_privilege function
+
+-- superuser
+\c -
+SELECT has_largeobject_privilege(1001, 'SELECT');
+SELECT has_largeobject_privilege(1002, 'SELECT');
+SELECT has_largeobject_privilege(1003, 'SELECT');
+SELECT has_largeobject_privilege(1004, 'SELECT');
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+SELECT has_largeobject_privilege(1002, 'UPDATE');
+SELECT has_largeobject_privilege(1003, 'UPDATE');
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+
+-- not-existing large object
+SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL
+
+-- non-superuser
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT has_largeobject_privilege(1001, 'SELECT');
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+SELECT has_largeobject_privilege(1003, 'SELECT');
+SELECT has_largeobject_privilege(1004, 'SELECT');
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT');
+SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT');
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false
+SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE');
+
-- compatibility mode in largeobject permission
\c -
SET lo_compat_privileges = false; -- default setting
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+
SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied
SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied
@@ -1346,6 +1385,9 @@ SELECT lo_import('/dev/null', 2003); -- to be denied
SET lo_compat_privileges = true; -- compatibility mode
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- true
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true
+
SELECT loread(lo_open(1002, x'40000'::int), 32);
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd');
SELECT lo_truncate(lo_open(1002, x'20000'::int), 10);
--
2.34.1
v2-0001-Deduplicate-codes-of-LargeObjectExists-and-mvLarg.patchtext/x-diff; name=v2-0001-Deduplicate-codes-of-LargeObjectExists-and-mvLarg.patchDownload
From 13e78ef824618f0ee1d05ca887c2bfc1f23b7bd8 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 2 Jul 2024 15:12:09 +0900
Subject: [PATCH v2 1/2] Deduplicate codes of LargeObjectExists and
mvLargeObjectExists
myLargeObjectExists() and LargeObjectExists() share almost same codes
except to whether snapshot can be specified. To deduplicate it,
thie commit renames myLargeObjectExists() to LargeObjectExistsWithSnapshot()
and rewrites LargeObjectExists() to call this internally.
---
src/backend/catalog/pg_largeobject.c | 13 +++++--
src/backend/storage/large_object/inv_api.c | 40 +---------------------
src/include/catalog/pg_largeobject.h | 2 ++
3 files changed, 13 insertions(+), 42 deletions(-)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..5d9fdfbd4c 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -14,8 +14,6 @@
*/
#include "postgres.h"
-#include "access/genam.h"
-#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
@@ -153,6 +151,15 @@ LargeObjectDrop(Oid loid)
*/
bool
LargeObjectExists(Oid loid)
+{
+ return LargeObjectExistsWithSnapshot(loid, NULL);
+}
+
+/*
+ * Same as LargeObjectExists(), except snapshot to read with can be specified.
+ */
+bool
+LargeObjectExistsWithSnapshot(Oid loid, Snapshot snapshot)
{
Relation pg_lo_meta;
ScanKeyData skey[1];
@@ -170,7 +177,7 @@ LargeObjectExists(Oid loid)
sd = systable_beginscan(pg_lo_meta,
LargeObjectMetadataOidIndexId, true,
- NULL, 1, skey);
+ snapshot, 1, skey);
tuple = systable_getnext(sd);
if (HeapTupleIsValid(tuple))
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index f951083324..afce51c167 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -41,7 +41,6 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_largeobject.h"
-#include "catalog/pg_largeobject_metadata.h"
#include "libpq/libpq-fs.h"
#include "miscadmin.h"
#include "storage/large_object.h"
@@ -123,43 +122,6 @@ close_lo_relation(bool isCommit)
}
-/*
- * Same as pg_largeobject.c's LargeObjectExists(), except snapshot to
- * read with can be specified.
- */
-static bool
-myLargeObjectExists(Oid loid, Snapshot snapshot)
-{
- Relation pg_lo_meta;
- ScanKeyData skey[1];
- SysScanDesc sd;
- HeapTuple tuple;
- bool retval = false;
-
- ScanKeyInit(&skey[0],
- Anum_pg_largeobject_metadata_oid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(loid));
-
- pg_lo_meta = table_open(LargeObjectMetadataRelationId,
- AccessShareLock);
-
- sd = systable_beginscan(pg_lo_meta,
- LargeObjectMetadataOidIndexId, true,
- snapshot, 1, skey);
-
- tuple = systable_getnext(sd);
- if (HeapTupleIsValid(tuple))
- retval = true;
-
- systable_endscan(sd);
-
- table_close(pg_lo_meta, AccessShareLock);
-
- return retval;
-}
-
-
/*
* Extract data field from a pg_largeobject tuple, detoasting if needed
* and verifying that the length is sane. Returns data pointer (a bytea *),
@@ -279,7 +241,7 @@ inv_open(Oid lobjId, int flags, MemoryContext mcxt)
snapshot = GetActiveSnapshot();
/* Can't use LargeObjectExists here because we need to specify snapshot */
- if (!myLargeObjectExists(lobjId, snapshot))
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("large object %u does not exist", lobjId)));
diff --git a/src/include/catalog/pg_largeobject.h b/src/include/catalog/pg_largeobject.h
index b40c90b749..e684c58ca9 100644
--- a/src/include/catalog/pg_largeobject.h
+++ b/src/include/catalog/pg_largeobject.h
@@ -20,6 +20,7 @@
#include "catalog/genbki.h"
#include "catalog/pg_largeobject_d.h"
+#include "utils/snapshot.h"
/* ----------------
* pg_largeobject definition. cpp turns this into
@@ -49,5 +50,6 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_largeobject_loid_pn_index, 2683, LargeObjectLOidPNI
extern Oid LargeObjectCreate(Oid loid);
extern void LargeObjectDrop(Oid loid);
extern bool LargeObjectExists(Oid loid);
+extern bool LargeObjectExistsWithSnapshot(Oid loid, Snapshot snapshot);
#endif /* PG_LARGEOBJECT_H */
--
2.34.1
On 2024/09/10 17:45, Yugo NAGATA wrote:
On Mon, 9 Sep 2024 22:59:34 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:On 2024/07/02 16:34, Yugo NAGATA wrote:
So, I would like to propose to add
has_large_object_function for checking if a user has the privilege on a large
object.+1
Thank you for your looking into this!
I've attached a updated patch.
Thanks for updating the patches! LGTM, except for a couple of minor things:
+okui chiba * has_largeobject_privilege_id
"okui chiba" seems to be a garbage text accidentally added.
+ * role by Oid, large object by id, and privileges as AclMode.
"Oid" seems better than "id" in "large object by id".
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Thu, 12 Sep 2024 19:07:22 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
On 2024/09/10 17:45, Yugo NAGATA wrote:
On Mon, 9 Sep 2024 22:59:34 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:On 2024/07/02 16:34, Yugo NAGATA wrote:
So, I would like to propose to add
has_large_object_function for checking if a user has the privilege on a large
object.+1
Thank you for your looking into this!
I've attached a updated patch.Thanks for updating the patches! LGTM, except for a couple of minor things:
+okui chiba * has_largeobject_privilege_id
"okui chiba" seems to be a garbage text accidentally added.
+ * role by Oid, large object by id, and privileges as AclMode.
"Oid" seems better than "id" in "large object by id".
Thank you for pointing out it.
I've fixed them and attached the updated patch, v3.
Regards,
Yugo Nagata
--
Yugo NAGATA <nagata@sraoss.co.jp>
Attachments:
v3-0002-Add-has_largeobject_privilege-function.patchtext/x-diff; name=v3-0002-Add-has_largeobject_privilege-function.patchDownload
From 97bc1f94c9e8be00f2649660f1f44d78fcc78653 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 2 Jul 2024 15:12:17 +0900
Subject: [PATCH v3 2/2] Add has_largeobject_privilege function
This function is for checking whether a user has the privilege on a
large object. There are three variations whose arguments are combinations
of large object OID with user name, user OID, or implicit user (current_user).
It returns NULL if not-existing large object id is specified, and raises an
error if non-existing user name is specified. These behavior is similar
with has_table_privilege.
---
doc/src/sgml/func.sgml | 18 +++
src/backend/utils/adt/acl.c | 140 ++++++++++++++++++++
src/include/catalog/pg_proc.dat | 13 ++
src/test/regress/expected/privileges.out | 162 +++++++++++++++++++++++
src/test/regress/sql/privileges.sql | 42 ++++++
5 files changed, 375 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 1bde4091ca..53b602b6e3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25117,6 +25117,24 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>has_largeobject_privilege</primary>
+ </indexterm>
+ <function>has_largeobject_privilege</function> (
+ <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+ <parameter>largeobject</parameter> <type>oid</type>,
+ <parameter>privilege</parameter> <type>text</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does user have privilege for large object?
+ Allowable privilege types are
+ <literal>SELECT</literal> and <literal>UPDATE</literal>.
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index d7b39140b3..35ecef6eaa 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -26,6 +26,7 @@
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
+#include "catalog/pg_largeobject.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_tablespace.h"
@@ -39,6 +40,7 @@
#include "lib/bloomfilter.h"
#include "lib/qunique.h"
#include "miscadmin.h"
+#include "storage/large_object.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
@@ -46,6 +48,7 @@
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/varlena.h"
@@ -124,6 +127,7 @@ static AclMode convert_tablespace_priv_string(text *priv_type_text);
static Oid convert_type_name(text *typename);
static AclMode convert_type_priv_string(text *priv_type_text);
static AclMode convert_parameter_priv_string(text *priv_text);
+static AclMode convert_largeobject_priv_string(text *priv_text);
static AclMode convert_role_priv_string(text *priv_type_text);
static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
@@ -4669,6 +4673,142 @@ convert_parameter_priv_string(text *priv_text)
return convert_any_priv_string(priv_text, parameter_priv_map);
}
+/*
+ * has_largeobject_privilege variants
+ * These are all named "has_largeobject_privilege" at the SQL level.
+ * They take various combinations of large object OID with
+ * user name, user OID, or implicit user = current_user.
+ *
+ * The result is a boolean value: true if user has the indicated
+ * privilege, false if not, or NULL if object doesn't exist.
+ */
+
+/*
+ * has_lo_priv_byid
+ *
+ * Helper function to check user privileges on a large object given the
+ * role by Oid, large object by Oid, and privileges as AclMode.
+ */
+static bool
+has_lo_priv_byid(Oid roleid, Oid lobjId, AclMode priv, bool *is_missing)
+{
+ Snapshot snapshot = NULL;
+ AclResult aclresult;
+
+ if (priv & ACL_UPDATE)
+ snapshot = NULL;
+ else
+ snapshot = GetActiveSnapshot();
+
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
+ {
+ Assert(is_missing != NULL);
+ *is_missing = true;
+ return false;
+ }
+
+ if (lo_compat_privileges)
+ return true;
+
+ aclresult = pg_largeobject_aclcheck_snapshot(lobjId,
+ roleid,
+ priv,
+ snapshot);
+ return aclresult == ACLCHECK_OK;
+}
+
+/*
+ * has_largeobject_privilege_name_id
+ * Check user privileges on a large object given
+ * name username, large object oid, and text priv name.
+ */
+Datum
+has_largeobject_privilege_name_id(PG_FUNCTION_ARGS)
+{
+ Name username = PG_GETARG_NAME(0);
+ Oid roleid = get_role_oid_or_public(NameStr(*username));
+ Oid lobjId = PG_GETARG_OID(1);
+ text *priv_type_text = PG_GETARG_TEXT_PP(2);
+ AclMode mode;
+ bool is_missing = false;
+ bool result;
+
+ mode = convert_largeobject_priv_string(priv_type_text);
+ result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing);
+
+ if (is_missing)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * has_largeobject_privilege_id
+ * Check user privileges on a large object given
+ * large object oid, and text priv name.
+ * current_user is assumed
+ */
+Datum
+has_largeobject_privilege_id(PG_FUNCTION_ARGS)
+{
+ Oid lobjId = PG_GETARG_OID(0);
+ Oid roleid = GetUserId();
+ text *priv_type_text = PG_GETARG_TEXT_PP(1);
+ AclMode mode;
+ bool is_missing = false;
+ bool result;
+
+ mode = convert_largeobject_priv_string(priv_type_text);
+ result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing);
+
+ if (is_missing)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * has_largeobject_privilege_id_id
+ * Check user privileges on a large object given
+ * roleid, large object oid, and text priv name.
+ */
+Datum
+has_largeobject_privilege_id_id(PG_FUNCTION_ARGS)
+{
+ Oid roleid = PG_GETARG_OID(0);
+ Oid lobjId = PG_GETARG_OID(1);
+ text *priv_type_text = PG_GETARG_TEXT_PP(2);
+ AclMode mode;
+ bool is_missing = false;
+ bool result;
+
+ mode = convert_largeobject_priv_string(priv_type_text);
+ result = has_lo_priv_byid(roleid, lobjId, mode, &is_missing);
+
+ if (is_missing)
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(result);
+}
+
+/*
+ * convert_largeobject_priv_string
+ * Convert text string to AclMode value.
+ */
+static AclMode
+convert_largeobject_priv_string(text *priv_type_text)
+{
+ static const priv_map largeobject_priv_map[] = {
+ {"SELECT", ACL_SELECT},
+ {"SELECT WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SELECT)},
+ {"UPDATE", ACL_UPDATE},
+ {"UPDATE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_UPDATE)},
+ {NULL, 0}
+ };
+
+ return convert_any_priv_string(priv_type_text, largeobject_priv_map);
+}
+
/*
* pg_has_role variants
* These are all named "pg_has_role" at the SQL level.
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ff5436acac..d1da9a8947 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5333,6 +5333,19 @@
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_any_column_privilege_id' },
+{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
+ proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'name oid text',
+ prosrc => 'has_largeobject_privilege_name_id' },
+{ oid => '4552', descr => 'current privilege on large objct by large object oid',
+ proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid text',
+ prosrc => 'has_largeobject_privilege_id' },
+{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
+ proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
+ prorettype => 'bool', proargtypes => 'oid oid text',
+ prosrc => 'has_largeobject_privilege_id_id' },
+
{ oid => '3355', descr => 'I/O',
proname => 'pg_ndistinct_in', prorettype => 'pg_ndistinct',
proargtypes => 'cstring', prosrc => 'pg_ndistinct_in' },
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index fab0cc800f..f0506951da 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2077,10 +2077,160 @@ SELECT lo_truncate(lo_open(2001, x'20000'::int), 10);
0
(1 row)
+-- has_largeobject_privilege function
+-- superuser
+\c -
+SELECT has_largeobject_privilege(1001, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+-- not-existing large object
+SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL
+ has_largeobject_privilege
+---------------------------
+
+(1 row)
+
+-- non-superuser
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT has_largeobject_privilege(1001, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE');
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
-- compatibility mode in largeobject permission
\c -
SET lo_compat_privileges = false; -- default setting
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+ has_largeobject_privilege
+---------------------------
+ f
+(1 row)
+
SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied
ERROR: permission denied for large object 1002
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied
@@ -2100,6 +2250,18 @@ ERROR: permission denied for function lo_import
\c -
SET lo_compat_privileges = true; -- compatibility mode
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- true
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true
+ has_largeobject_privilege
+---------------------------
+ t
+(1 row)
+
SELECT loread(lo_open(1002, x'40000'::int), 32);
loread
--------
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index ae338e8cc8..b8101ea6fa 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1328,11 +1328,50 @@ SELECT loread(lo_open(1005, x'40000'::int), 32);
SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied
SELECT lo_truncate(lo_open(2001, x'20000'::int), 10);
+-- has_largeobject_privilege function
+
+-- superuser
+\c -
+SELECT has_largeobject_privilege(1001, 'SELECT');
+SELECT has_largeobject_privilege(1002, 'SELECT');
+SELECT has_largeobject_privilege(1003, 'SELECT');
+SELECT has_largeobject_privilege(1004, 'SELECT');
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+SELECT has_largeobject_privilege(1002, 'UPDATE');
+SELECT has_largeobject_privilege(1003, 'UPDATE');
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+
+-- not-existing large object
+SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL
+
+-- non-superuser
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT has_largeobject_privilege(1001, 'SELECT');
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+SELECT has_largeobject_privilege(1003, 'SELECT');
+SELECT has_largeobject_privilege(1004, 'SELECT');
+
+SELECT has_largeobject_privilege(1001, 'UPDATE');
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false
+SELECT has_largeobject_privilege(1004, 'UPDATE');
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT');
+SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT');
+
+SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false
+SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE');
+
-- compatibility mode in largeobject permission
\c -
SET lo_compat_privileges = false; -- default setting
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- false
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false
+
SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied
SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied
@@ -1346,6 +1385,9 @@ SELECT lo_import('/dev/null', 2003); -- to be denied
SET lo_compat_privileges = true; -- compatibility mode
SET SESSION AUTHORIZATION regress_priv_user4;
+SELECT has_largeobject_privilege(1002, 'SELECT'); -- true
+SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true
+
SELECT loread(lo_open(1002, x'40000'::int), 32);
SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd');
SELECT lo_truncate(lo_open(1002, x'20000'::int), 10);
--
2.34.1
v3-0001-Deduplicate-codes-of-LargeObjectExists-and-mvLarg.patchtext/x-diff; name=v3-0001-Deduplicate-codes-of-LargeObjectExists-and-mvLarg.patchDownload
From 04d87ad2ed1fde8df94bbf2c6f8446cc20d73b9a Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 2 Jul 2024 15:12:09 +0900
Subject: [PATCH v3 1/2] Deduplicate codes of LargeObjectExists and
mvLargeObjectExists
myLargeObjectExists() and LargeObjectExists() share almost same codes
except to whether snapshot can be specified. To deduplicate it,
thie commit renames myLargeObjectExists() to LargeObjectExistsWithSnapshot()
and rewrites LargeObjectExists() to call this internally.
---
src/backend/catalog/pg_largeobject.c | 13 +++++--
src/backend/storage/large_object/inv_api.c | 40 +---------------------
src/include/catalog/pg_largeobject.h | 2 ++
3 files changed, 13 insertions(+), 42 deletions(-)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..5d9fdfbd4c 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -14,8 +14,6 @@
*/
#include "postgres.h"
-#include "access/genam.h"
-#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/indexing.h"
@@ -153,6 +151,15 @@ LargeObjectDrop(Oid loid)
*/
bool
LargeObjectExists(Oid loid)
+{
+ return LargeObjectExistsWithSnapshot(loid, NULL);
+}
+
+/*
+ * Same as LargeObjectExists(), except snapshot to read with can be specified.
+ */
+bool
+LargeObjectExistsWithSnapshot(Oid loid, Snapshot snapshot)
{
Relation pg_lo_meta;
ScanKeyData skey[1];
@@ -170,7 +177,7 @@ LargeObjectExists(Oid loid)
sd = systable_beginscan(pg_lo_meta,
LargeObjectMetadataOidIndexId, true,
- NULL, 1, skey);
+ snapshot, 1, skey);
tuple = systable_getnext(sd);
if (HeapTupleIsValid(tuple))
diff --git a/src/backend/storage/large_object/inv_api.c b/src/backend/storage/large_object/inv_api.c
index f951083324..afce51c167 100644
--- a/src/backend/storage/large_object/inv_api.c
+++ b/src/backend/storage/large_object/inv_api.c
@@ -41,7 +41,6 @@
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_largeobject.h"
-#include "catalog/pg_largeobject_metadata.h"
#include "libpq/libpq-fs.h"
#include "miscadmin.h"
#include "storage/large_object.h"
@@ -123,43 +122,6 @@ close_lo_relation(bool isCommit)
}
-/*
- * Same as pg_largeobject.c's LargeObjectExists(), except snapshot to
- * read with can be specified.
- */
-static bool
-myLargeObjectExists(Oid loid, Snapshot snapshot)
-{
- Relation pg_lo_meta;
- ScanKeyData skey[1];
- SysScanDesc sd;
- HeapTuple tuple;
- bool retval = false;
-
- ScanKeyInit(&skey[0],
- Anum_pg_largeobject_metadata_oid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(loid));
-
- pg_lo_meta = table_open(LargeObjectMetadataRelationId,
- AccessShareLock);
-
- sd = systable_beginscan(pg_lo_meta,
- LargeObjectMetadataOidIndexId, true,
- snapshot, 1, skey);
-
- tuple = systable_getnext(sd);
- if (HeapTupleIsValid(tuple))
- retval = true;
-
- systable_endscan(sd);
-
- table_close(pg_lo_meta, AccessShareLock);
-
- return retval;
-}
-
-
/*
* Extract data field from a pg_largeobject tuple, detoasting if needed
* and verifying that the length is sane. Returns data pointer (a bytea *),
@@ -279,7 +241,7 @@ inv_open(Oid lobjId, int flags, MemoryContext mcxt)
snapshot = GetActiveSnapshot();
/* Can't use LargeObjectExists here because we need to specify snapshot */
- if (!myLargeObjectExists(lobjId, snapshot))
+ if (!LargeObjectExistsWithSnapshot(lobjId, snapshot))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("large object %u does not exist", lobjId)));
diff --git a/src/include/catalog/pg_largeobject.h b/src/include/catalog/pg_largeobject.h
index b40c90b749..e684c58ca9 100644
--- a/src/include/catalog/pg_largeobject.h
+++ b/src/include/catalog/pg_largeobject.h
@@ -20,6 +20,7 @@
#include "catalog/genbki.h"
#include "catalog/pg_largeobject_d.h"
+#include "utils/snapshot.h"
/* ----------------
* pg_largeobject definition. cpp turns this into
@@ -49,5 +50,6 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_largeobject_loid_pn_index, 2683, LargeObjectLOidPNI
extern Oid LargeObjectCreate(Oid loid);
extern void LargeObjectDrop(Oid loid);
extern bool LargeObjectExists(Oid loid);
+extern bool LargeObjectExistsWithSnapshot(Oid loid, Snapshot snapshot);
#endif /* PG_LARGEOBJECT_H */
--
2.34.1
On Thu, 12 Sep 2024 19:51:33 +0900
Yugo NAGATA <nagata@sraoss.co.jp> wrote:
On Thu, 12 Sep 2024 19:07:22 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:On 2024/09/10 17:45, Yugo NAGATA wrote:
On Mon, 9 Sep 2024 22:59:34 +0900
Fujii Masao <masao.fujii@oss.nttdata.com> wrote:On 2024/07/02 16:34, Yugo NAGATA wrote:
So, I would like to propose to add
has_large_object_function for checking if a user has the privilege on a large
object.+1
Thank you for your looking into this!
I've attached a updated patch.Thanks for updating the patches! LGTM, except for a couple of minor things:
+okui chiba * has_largeobject_privilege_id
"okui chiba" seems to be a garbage text accidentally added.
+ * role by Oid, large object by id, and privileges as AclMode.
"Oid" seems better than "id" in "large object by id".
Thank you for pointing out it.
I've fixed them and attached the updated patch, v3.
I confirmed the patches are committed in the master branch.
Thank you!
I've updated the commitfest status to "committed".
Regards,
Yugo Nagata
--
Yugo Nagata <nagata@sraoss.co.jp>
On Fri, Sep 13, 2024 at 03:56:11PM +0900, Yugo Nagata wrote:
I confirmed the patches are committed in the master branch.
Thank you!I've updated the commitfest status to "committed".
This patch has been committed as of 4eada203a5a8, and introduced this
block in pg_proc.dat:
{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'name oid text',
prosrc => 'has_largeobject_privilege_name_id' },
{ oid => '4552', descr => 'current privilege on large objct by large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_largeobject_privilege_id' },
{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid oid text',
prosrc => 'has_largeobject_privilege_id_id' },
This has a couple of mistakes:
- New functions introduced during a development cycle should use OIDs in
the range 8000-9999. See 98eab30b93d5, consisting of running
./unused_oids to get a random range.
- The function descriptions are inconsistent and have the three same
typos:
-- s/large objct/large object/.
-- s/current privilege/current user privilege/ for the second entry.
And while that's not mandatory for committers, I would apply a
reformat-dat-files while on it, to reduce some diffs I'm seeing.
This results in the attached that I'd like to apply to fix all that. It
needs a catalog version bump, of course.
--
Michael
Attachments:
0001-Fix-inconsistencies-with-catalog-data-of-new-LO-priv.patchtext/x-diff; charset=us-asciiDownload
From a2b886a277a9f4a0e4059f53ff78631d8e9e2abb Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 26 Sep 2024 17:14:45 +0900
Subject: [PATCH] Fix inconsistencies with catalog data of new LO privilege
functions
XXX: catversion bump!!
---
src/include/catalog/pg_proc.dat | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43f608d7a0..72bc4daed3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5369,15 +5369,18 @@
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_any_column_privilege_id' },
-{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
+{ oid => '8048',
+ descr => 'user privilege on large object by username, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'name oid text',
prosrc => 'has_largeobject_privilege_name_id' },
-{ oid => '4552', descr => 'current privilege on large objct by large object oid',
+{ oid => '8049',
+ descr => 'current user privilege on large object by large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_largeobject_privilege_id' },
-{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
+{ oid => '8050',
+ descr => 'user privilege on large object by user oid, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid oid text',
prosrc => 'has_largeobject_privilege_id_id' },
--
2.45.2
On 2024/09/26 17:16, Michael Paquier wrote:
On Fri, Sep 13, 2024 at 03:56:11PM +0900, Yugo Nagata wrote:
I confirmed the patches are committed in the master branch.
Thank you!I've updated the commitfest status to "committed".
This patch has been committed as of 4eada203a5a8, and introduced this
block in pg_proc.dat:{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'name oid text',
prosrc => 'has_largeobject_privilege_name_id' },
{ oid => '4552', descr => 'current privilege on large objct by large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_largeobject_privilege_id' },
{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid oid text',
prosrc => 'has_largeobject_privilege_id_id' },This has a couple of mistakes:
- New functions introduced during a development cycle should use OIDs in
the range 8000-9999. See 98eab30b93d5, consisting of running
./unused_oids to get a random range.
- The function descriptions are inconsistent and have the three same
typos:
-- s/large objct/large object/.
-- s/current privilege/current user privilege/ for the second entry.
I agree these issues need to be fixed. Thanks for the patch!
And while that's not mandatory for committers, I would apply a
reformat-dat-files while on it, to reduce some diffs I'm seeing.
Yes, that sounds better.
This results in the attached that I'd like to apply to fix all that. It
needs a catalog version bump, of course.
So, are you planning to commit the patch? If not, I'm happy to handle it!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
On Thu, Sep 26, 2024 at 07:51:06PM +0900, Fujii Masao wrote:
So, are you planning to commit the patch? If not, I'm happy to handle it!
I have some time to look at the buildfarm today, so I'll just go do it
now. Thanks for checking the patch.
--
Michael
On Thu, 26 Sep 2024 17:16:07 +0900
Michael Paquier <michael@paquier.xyz> wrote:
On Fri, Sep 13, 2024 at 03:56:11PM +0900, Yugo Nagata wrote:
I confirmed the patches are committed in the master branch.
Thank you!I've updated the commitfest status to "committed".
This patch has been committed as of 4eada203a5a8, and introduced this
block in pg_proc.dat:{ oid => '4551', descr => 'user privilege on large objct by username, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'name oid text',
prosrc => 'has_largeobject_privilege_name_id' },
{ oid => '4552', descr => 'current privilege on large objct by large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid text',
prosrc => 'has_largeobject_privilege_id' },
{ oid => '4553', descr => 'user privilege on large objct by user oid, large object oid',
proname => 'has_largeobject_privilege', procost => '10', provolatile => 's',
prorettype => 'bool', proargtypes => 'oid oid text',
prosrc => 'has_largeobject_privilege_id_id' },This has a couple of mistakes:
- New functions introduced during a development cycle should use OIDs in
the range 8000-9999. See 98eab30b93d5, consisting of running
./unused_oids to get a random range.
- The function descriptions are inconsistent and have the three same
typos:
-- s/large objct/large object/.
-- s/current privilege/current user privilege/ for the second entry.
Thank you for pointing out them.
I used unused_oids to look for available oids, but I missed to
follow the best practice suggested by it. I'll be careful.
Regards,
Yugo Nagata
And while that's not mandatory for committers, I would apply a
reformat-dat-files while on it, to reduce some diffs I'm seeing.This results in the attached that I'd like to apply to fix all that. It
needs a catalog version bump, of course.
--
Michael
--
Yugo NAGATA <nagata@sraoss.co.jp>
Michael Paquier <michael@paquier.xyz> writes:
- New functions introduced during a development cycle should use OIDs in
the range 8000-9999. See 98eab30b93d5, consisting of running
./unused_oids to get a random range.
There's been seen several fixups of this kind recently. Should we add a
note about this to the comment at the top of all of the pg_*.dat files
that have explicit oid assignments? People might be more likely to
notice that than the the section over in bki.sgml.
- ilmari
On Fri, 27 Sep 2024 11:44:25 +0100
Dagfinn Ilmari Mannsåker <ilmari@ilmari.org> wrote:
Michael Paquier <michael@paquier.xyz> writes:
- New functions introduced during a development cycle should use OIDs in
the range 8000-9999. See 98eab30b93d5, consisting of running
./unused_oids to get a random range.There's been seen several fixups of this kind recently. Should we add a
note about this to the comment at the top of all of the pg_*.dat files
that have explicit oid assignments? People might be more likely to
notice that than the the section over in bki.sgml.
How about adding more to unused_oids output to explain the reason why
patches should use consecutive OIDs in the range 8000-9999 and low OIDs
should not be used in patches(that is, this minimizes the risk of OID
collisions with other patches) instead of just saying it is the best practise.
I think patch authors looking for OIDs they can use will run unused_oids,
so more likely notice this.
Regards,
Yugo Nagata
--
Yugo Nagata <nagata@sraoss.co.jp>