public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v5 12/15] hio: Use ExtendBufferedRelBy()
33+ messages / 7 participants
[nested] [flat]
* [PATCH v5 12/15] hio: Use ExtendBufferedRelBy()
@ 2022-10-26 21:14 Andres Freund <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Andres Freund @ 2022-10-26 21:14 UTC (permalink / raw)
---
src/backend/access/heap/hio.c | 285 +++++++++++++++++-----------------
1 file changed, 146 insertions(+), 139 deletions(-)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 65886839e7..48cfcff975 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -354,6 +270,9 @@ RelationGetBufferForTuple(Relation relation, Size len,
so in RelationGetBufferForTuple() up above where your changes start,
there is this code
/*
* We first try to put the tuple on the same page we last inserted a tuple
* on, as cached in the BulkInsertState or relcache entry. If that
* doesn't work, we ask the Free Space Map to locate a suitable page.
* Since the FSM's info might be out of date, we have to be prepared to
* loop around and retry multiple times. (To insure this isn't an infinite
* loop, we must update the FSM with the correct amount of free space on
* each page that proves not to be suitable.) If the FSM has no record of
* a page with enough free space, we give up and extend the relation.
*
* When use_fsm is false, we either put the tuple onto the existing target
* page or extend the relation.
*/
if (bistate && bistate->current_buf != InvalidBuffer)
{
targetBlock = BufferGetBlockNumber(bistate->current_buf);
}
else
targetBlock = RelationGetTargetBlock(relation);
if (targetBlock == InvalidBlockNumber && use_fsm)
{
/*
* We have no cached target page, so ask the FSM for an initial
* target.
*/
targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
}
And, I was thinking how, ReadBufferBI() only has one caller now
(RelationGetBufferForTuple()) and, this caller basically already has
checked for the case in the inside of ReadBufferBI() (the code I pasted
above)
/* If we have the desired block already pinned, re-pin and return it */
if (bistate->current_buf != InvalidBuffer)
{
if (BufferGetBlockNumber(bistate->current_buf) == targetBlock)
{
/*
* Currently the LOCK variants are only used for extending
* relation, which should never reach this branch.
*/
Assert(mode != RBM_ZERO_AND_LOCK &&
mode != RBM_ZERO_AND_CLEANUP_LOCK);
IncrBufferRefCount(bistate->current_buf);
return bistate->current_buf;
}
/* ... else drop the old buffer */
So, I was thinking maybe there is some way to inline the logic for
ReadBufferBI(), because I think it would feel more streamlined to me.
@@ -558,18 +477,46 @@ loop:
ReleaseBuffer(buffer);
}
Oh, and I forget which commit introduced BulkInsertState->next_free and
last_free, but I remember thinking that it didn't seem to fit with the
other parts of that commit.
- /* Without FSM, always fall out of the loop and extend */
- if (!use_fsm)
- break;
+ if (bistate
+ && bistate->next_free != InvalidBlockNumber
+ && bistate->next_free <= bistate->last_free)
+ {
+ /*
+ * We bulk extended the relation before, and there are still some
+ * unused pages from that extension, so we don't need to look in
+ * the FSM for a new page. But do record the free space from the
+ * last page, somebody might insert narrower tuples later.
+ */
Why couldn't we have found out that we bulk-extended before and get the
block from there up above the while loop?
+ if (use_fsm)
+ RecordPageWithFreeSpace(relation, targetBlock, pageFreeSpace);
- /*
- * Update FSM as to condition of this page, and ask for another page
- * to try.
- */
- targetBlock = RecordAndGetPageWithFreeSpace(relation,
- targetBlock,
- pageFreeSpace,
- targetFreeSpace);
+ Assert(bistate->last_free != InvalidBlockNumber &&
You don't need the below half of the assert.
+ bistate->next_free <= bistate->last_free);
+ targetBlock = bistate->next_free;
+ if (bistate->next_free >= bistate->last_free)
they can only be equal at this point
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+ else
+ bistate->next_free++;
+ }
+ else if (!use_fsm)
+ {
+ /* Without FSM, always fall out of the loop and extend */
+ break;
+ }
It would be nice to have a comment explaining why this is in its own
else if instead of breaking earlier (i.e. !use_fsm is still a valid case
in the if branch above it)
+ else
+ {
+ /*
+ * Update FSM as to condition of this page, and ask for another
+ * page to try.
+ */
+ targetBlock = RecordAndGetPageWithFreeSpace(relation,
+ targetBlock,
+ pageFreeSpace,
+ targetFreeSpace);
+ }
we can get rid of needLock and waitcount variables like this
+#define MAX_BUFFERS 64
+ Buffer victim_buffers[MAX_BUFFERS];
+ BlockNumber firstBlock = InvalidBlockNumber;
+ BlockNumber firstBlockFSM = InvalidBlockNumber;
+ BlockNumber curBlock;
+ uint32 extend_by_pages;
+ uint32 no_fsm_pages;
+ uint32 waitcount;
+
+ extend_by_pages = num_pages;
+
+ /*
+ * Multiply the number of pages to extend by the number of waiters. Do
+ * this even if we're not using the FSM, as it does relieve
+ * contention. Pages will be found via bistate->next_free.
+ */
+ if (needLock)
+ waitcount = RelationExtensionLockWaiterCount(relation);
+ else
+ waitcount = 0;
+ extend_by_pages += extend_by_pages * waitcount;
if (!RELATION_IS_LOCAL(relation))
extend_by_pages += extend_by_pages *
RelationExtensionLockWaiterCount(relation);
+
+ /*
+ * can't extend by more than MAX_BUFFERS, we need to pin them all
+ * concurrently. FIXME: Need an NBuffers / MaxBackends type limit
+ * here.
+ */
+ extend_by_pages = Min(extend_by_pages, MAX_BUFFERS);
+
+ /*
+ * How many of the extended pages not to enter into the FSM.
+ *
+ * Only enter pages that we don't need ourselves into the FSM.
+ * Otherwise every other backend will immediately try to use the pages
+ * this backend neds itself, causing unnecessary contention.
+ *
+ * Bulk extended pages are remembered in bistate->next_free_buffer. So
+ * without a bistate we can't directly make use of them.
+ *
+ * Never enter the page returned into the FSM, we'll immediately use
+ * it.
+ */
+ if (num_pages > 1 && bistate == NULL)
+ no_fsm_pages = 1;
+ else
+ no_fsm_pages = num_pages;
this is more clearly this:
no_fsm_pages = bistate == NULL ? 1 : num_pages;
- /*
- * Release the file-extension lock; it's now OK for someone else to extend
- * the relation some more.
- */
- if (needLock)
- UnlockRelationForExtension(relation, ExclusiveLock);
+ if (bistate)
+ {
+ if (extend_by_pages > 1)
+ {
+ bistate->next_free = firstBlock + 1;
+ bistate->last_free = firstBlock + extend_by_pages - 1;
+ }
+ else
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+ }
+
+ buffer = victim_buffers[0];
If we move buffer = up, we can have only one if (bistate)
+ if (bistate)
+ {
+ IncrBufferRefCount(buffer);
+ bistate->current_buf = buffer;
+ }
+ }
like this:
buffer = victim_buffers[0];
if (bistate)
{
if (extend_by_pages > 1)
{
bistate->next_free = firstBlock + 1;
bistate->last_free = firstBlock + extend_by_pages - 1;
}
else
{
bistate->next_free = InvalidBlockNumber;
bistate->last_free = InvalidBlockNumber;
}
IncrBufferRefCount(buffer);
bistate->current_buf = buffer;
}
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v4 12/15] hio: Use ExtendBufferedRelBy()
@ 2022-10-26 21:14 Andres Freund <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Andres Freund @ 2022-10-26 21:14 UTC (permalink / raw)
---
src/backend/access/heap/hio.c | 287 +++++++++++++++++-----------------
1 file changed, 147 insertions(+), 140 deletions(-)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 65886839e70..48cfcff975f 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -185,90 +185,6 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
}
}
-/*
- * Extend a relation by multiple blocks to avoid future contention on the
- * relation extension lock. Our goal is to pre-extend the relation by an
- * amount which ramps up as the degree of contention ramps up, but limiting
- * the result to some sane overall value.
- */
-static void
-RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
-{
- BlockNumber blockNum,
- firstBlock = InvalidBlockNumber;
- int extraBlocks;
- int lockWaiters;
-
- /* Use the length of the lock wait queue to judge how much to extend. */
- lockWaiters = RelationExtensionLockWaiterCount(relation);
- if (lockWaiters <= 0)
- return;
-
- /*
- * It might seem like multiplying the number of lock waiters by as much as
- * 20 is too aggressive, but benchmarking revealed that smaller numbers
- * were insufficient. 512 is just an arbitrary cap to prevent
- * pathological results.
- */
- extraBlocks = Min(512, lockWaiters * 20);
-
- do
- {
- Buffer buffer;
- Page page;
- Size freespace;
-
- /*
- * Extend by one page. This should generally match the main-line
- * extension code in RelationGetBufferForTuple, except that we hold
- * the relation extension lock throughout, and we don't immediately
- * initialize the page (see below).
- */
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
- page = BufferGetPage(buffer);
-
- if (!PageIsNew(page))
- elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- BufferGetBlockNumber(buffer),
- RelationGetRelationName(relation));
-
- /*
- * Add the page to the FSM without initializing. If we were to
- * initialize here, the page would potentially get flushed out to disk
- * before we add any useful content. There's no guarantee that that'd
- * happen before a potential crash, so we need to deal with
- * uninitialized pages anyway, thus avoid the potential for
- * unnecessary writes.
- */
-
- /* we'll need this info below */
- blockNum = BufferGetBlockNumber(buffer);
- freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData;
-
- UnlockReleaseBuffer(buffer);
-
- /* Remember first block number thus added. */
- if (firstBlock == InvalidBlockNumber)
- firstBlock = blockNum;
-
- /*
- * Immediately update the bottom level of the FSM. This has a good
- * chance of making this page visible to other concurrently inserting
- * backends, and we want that to happen without delay.
- */
- RecordPageWithFreeSpace(relation, blockNum, freespace);
- }
- while (--extraBlocks > 0);
-
- /*
- * Updating the upper levels of the free space map is too expensive to do
- * for every block, but it's worth doing once at the end to make sure that
- * subsequent insertion activity sees all of those nifty free pages we
- * just inserted.
- */
- FreeSpaceMapVacuumRange(relation, firstBlock, blockNum + 1);
-}
-
/*
* RelationGetBufferForTuple
*
@@ -354,6 +270,9 @@ RelationGetBufferForTuple(Relation relation, Size len,
len = MAXALIGN(len); /* be conservative */
+ if (num_pages <= 0)
+ num_pages = 1;
+
/* Bulk insert is not supported for updates, only inserts. */
Assert(otherBuffer == InvalidBuffer || !bistate);
@@ -558,18 +477,46 @@ loop:
ReleaseBuffer(buffer);
}
- /* Without FSM, always fall out of the loop and extend */
- if (!use_fsm)
- break;
+ if (bistate
+ && bistate->next_free != InvalidBlockNumber
+ && bistate->next_free <= bistate->last_free)
+ {
+ /*
+ * We bulk extended the relation before, and there are still some
+ * unused pages from that extension, so we don't need to look in
+ * the FSM for a new page. But do record the free space from the
+ * last page, somebody might insert narrower tuples later.
+ */
+ if (use_fsm)
+ RecordPageWithFreeSpace(relation, targetBlock, pageFreeSpace);
- /*
- * Update FSM as to condition of this page, and ask for another page
- * to try.
- */
- targetBlock = RecordAndGetPageWithFreeSpace(relation,
- targetBlock,
- pageFreeSpace,
- targetFreeSpace);
+ Assert(bistate->last_free != InvalidBlockNumber &&
+ bistate->next_free <= bistate->last_free);
+ targetBlock = bistate->next_free;
+ if (bistate->next_free >= bistate->last_free)
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+ else
+ bistate->next_free++;
+ }
+ else if (!use_fsm)
+ {
+ /* Without FSM, always fall out of the loop and extend */
+ break;
+ }
+ else
+ {
+ /*
+ * Update FSM as to condition of this page, and ask for another
+ * page to try.
+ */
+ targetBlock = RecordAndGetPageWithFreeSpace(relation,
+ targetBlock,
+ pageFreeSpace,
+ targetFreeSpace);
+ }
}
/*
@@ -582,60 +529,120 @@ loop:
*/
needLock = !RELATION_IS_LOCAL(relation);
- /*
- * If we need the lock but are not able to acquire it immediately, we'll
- * consider extending the relation by multiple blocks at a time to manage
- * contention on the relation extension lock. However, this only makes
- * sense if we're using the FSM; otherwise, there's no point.
- */
- if (needLock)
{
- if (!use_fsm)
- LockRelationForExtension(relation, ExclusiveLock);
- else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock))
+#define MAX_BUFFERS 64
+ Buffer victim_buffers[MAX_BUFFERS];
+ BlockNumber firstBlock = InvalidBlockNumber;
+ BlockNumber firstBlockFSM = InvalidBlockNumber;
+ BlockNumber curBlock;
+ uint32 extend_by_pages;
+ uint32 no_fsm_pages;
+ uint32 waitcount;
+
+ extend_by_pages = num_pages;
+
+ /*
+ * Multiply the number of pages to extend by the number of waiters. Do
+ * this even if we're not using the FSM, as it does relieve
+ * contention. Pages will be found via bistate->next_free.
+ */
+ if (needLock)
+ waitcount = RelationExtensionLockWaiterCount(relation);
+ else
+ waitcount = 0;
+ extend_by_pages += extend_by_pages * waitcount;
+
+ /*
+ * can't extend by more than MAX_BUFFERS, we need to pin them all
+ * concurrently. FIXME: Need an NBuffers / MaxBackends type limit
+ * here.
+ */
+ extend_by_pages = Min(extend_by_pages, MAX_BUFFERS);
+
+ /*
+ * How many of the extended pages not to enter into the FSM.
+ *
+ * Only enter pages that we don't need ourselves into the FSM.
+ * Otherwise every other backend will immediately try to use the pages
+ * this backend neds itself, causing unnecessary contention.
+ *
+ * Bulk extended pages are remembered in bistate->next_free_buffer. So
+ * without a bistate we can't directly make use of them.
+ *
+ * Never enter the page returned into the FSM, we'll immediately use
+ * it.
+ */
+ if (num_pages > 1 && bistate == NULL)
+ no_fsm_pages = 1;
+ else
+ no_fsm_pages = num_pages;
+
+ if (bistate && bistate->current_buf != InvalidBuffer)
{
- /* Couldn't get the lock immediately; wait for it. */
- LockRelationForExtension(relation, ExclusiveLock);
+ ReleaseBuffer(bistate->current_buf);
+ bistate->current_buf = InvalidBuffer;
+ }
- /*
- * Check if some other backend has extended a block for us while
- * we were waiting on the lock.
- */
- targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
+ firstBlock = ExtendBufferedRelBy(EB_REL(relation), MAIN_FORKNUM,
+ bistate ? bistate->strategy : NULL,
+ EB_LOCK_FIRST,
+ extend_by_pages,
+ victim_buffers,
+ &extend_by_pages);
- /*
- * If some other waiter has already extended the relation, we
- * don't need to do so; just use the existing freespace.
- */
- if (targetBlock != InvalidBlockNumber)
+ /*
+ * Relation is now extended. Make all but the first buffer available
+ * to other backends.
+ *
+ * XXX: We don't necessarily need to release pin / update FSM while
+ * holding the extension lock. But there are some advantages.
+ */
+ curBlock = firstBlock;
+ for (uint32 i = 0; i < extend_by_pages; i++, curBlock++)
+ {
+ Assert(curBlock == BufferGetBlockNumber(victim_buffers[i]));
+ Assert(BlockNumberIsValid(curBlock));
+
+ /* don't release the pin on the page returned by this function */
+ if (i > 0)
+ ReleaseBuffer(victim_buffers[i]);
+
+ if (i >= no_fsm_pages && use_fsm)
{
- UnlockRelationForExtension(relation, ExclusiveLock);
- goto loop;
- }
+ if (firstBlockFSM == InvalidBlockNumber)
+ firstBlockFSM = curBlock;
- /* Time to bulk-extend. */
- RelationAddExtraBlocks(relation, bistate);
+ RecordPageWithFreeSpace(relation,
+ curBlock,
+ BufferGetPageSize(victim_buffers[i]) - SizeOfPageHeaderData);
+ }
+ }
+
+ if (use_fsm && firstBlockFSM != InvalidBlockNumber)
+ FreeSpaceMapVacuumRange(relation, firstBlockFSM, firstBlock + num_pages);
+
+ if (bistate)
+ {
+ if (extend_by_pages > 1)
+ {
+ bistate->next_free = firstBlock + 1;
+ bistate->last_free = firstBlock + extend_by_pages - 1;
+ }
+ else
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+ }
+
+ buffer = victim_buffers[0];
+ if (bistate)
+ {
+ IncrBufferRefCount(buffer);
+ bistate->current_buf = buffer;
}
}
- /*
- * In addition to whatever extension we performed above, we always add at
- * least one block to satisfy our own request.
- *
- * XXX This does an lseek - rather expensive - but at the moment it is the
- * only way to accurately determine how many blocks are in a relation. Is
- * it worth keeping an accurate file length in shared memory someplace,
- * rather than relying on the kernel to do it for us?
- */
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
-
- /*
- * Release the file-extension lock; it's now OK for someone else to extend
- * the relation some more.
- */
- if (needLock)
- UnlockRelationForExtension(relation, ExclusiveLock);
-
/*
* We need to initialize the empty new page. Double-check that it really
* is empty (this should never happen, but if it does we don't want to
--
2.38.0
--njltjzibte523gwd
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0013-bufmgr-debug-Add-PrintBuffer-Desc.patch"
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v5 11/14] hio: Use ExtendBufferedRelBy()
@ 2022-10-26 21:14 Andres Freund <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Andres Freund @ 2022-10-26 21:14 UTC (permalink / raw)
---
src/backend/access/heap/hio.c | 332 ++++++++++++++++++++--------------
1 file changed, 194 insertions(+), 138 deletions(-)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 65886839e70..40f53d7177e 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -186,89 +186,176 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
}
/*
- * Extend a relation by multiple blocks to avoid future contention on the
- * relation extension lock. Our goal is to pre-extend the relation by an
- * amount which ramps up as the degree of contention ramps up, but limiting
- * the result to some sane overall value.
+ * Extend the relation. By multiple pages, if beneficial.
+ *
+ * If the caller needs multiple pages (num_pages > 1), we always try to extend
+ * by at least that much.
+ *
+ * If there is contention on the extension lock, we don't just extend "for
+ * ourselves", but we try to help others. We can do so by adding empty pages
+ * into the FSM. Typically there is no contention when we can't use the FSM.
+ *
+ * We do have to limit the number of pages to extend by to some value, as the
+ * buffers for all the extended pages need to, temporarily, be pinned. For now
+ * we define MAX_BUFFERS_TO_EXTEND_BY to be 64 buffers, it's hard to see
+ * benefits with higher numbers. This partially is because copyfrom.c's
+ * MAX_BUFFERED_TUPLES / MAX_BUFFERED_BYTES prevents larger multi_inserts.
*/
-static void
-RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
+static Buffer
+RelationAddBlocks(Relation relation, BulkInsertState bistate,
+ int num_pages, bool use_fsm)
{
- BlockNumber blockNum,
- firstBlock = InvalidBlockNumber;
- int extraBlocks;
- int lockWaiters;
-
- /* Use the length of the lock wait queue to judge how much to extend. */
- lockWaiters = RelationExtensionLockWaiterCount(relation);
- if (lockWaiters <= 0)
- return;
+#define MAX_BUFFERS_TO_EXTEND_BY 64
+ Buffer victim_buffers[MAX_BUFFERS_TO_EXTEND_BY];
+ BlockNumber firstBlock = InvalidBlockNumber;
+ BlockNumber firstBlockFSM = InvalidBlockNumber;
+ uint32 extend_by_pages;
+ uint32 not_in_fsm_pages;
+ BlockNumber curBlock;
+ uint32 waitcount;
+ Buffer buffer;
/*
- * It might seem like multiplying the number of lock waiters by as much as
- * 20 is too aggressive, but benchmarking revealed that smaller numbers
- * were insufficient. 512 is just an arbitrary cap to prevent
- * pathological results.
+ * Determine by how many pages to try to extend by.
*/
- extraBlocks = Min(512, lockWaiters * 20);
-
- do
+ if (bistate == NULL && !use_fsm)
{
- Buffer buffer;
- Page page;
- Size freespace;
-
/*
- * Extend by one page. This should generally match the main-line
- * extension code in RelationGetBufferForTuple, except that we hold
- * the relation extension lock throughout, and we don't immediately
- * initialize the page (see below).
+ * If we have neither bistate, nor can use the FSM, we can't bulk
+ * extend - there'd be no way to find the additional pages.
*/
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
- page = BufferGetPage(buffer);
-
- if (!PageIsNew(page))
- elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- BufferGetBlockNumber(buffer),
- RelationGetRelationName(relation));
-
- /*
- * Add the page to the FSM without initializing. If we were to
- * initialize here, the page would potentially get flushed out to disk
- * before we add any useful content. There's no guarantee that that'd
- * happen before a potential crash, so we need to deal with
- * uninitialized pages anyway, thus avoid the potential for
- * unnecessary writes.
- */
-
- /* we'll need this info below */
- blockNum = BufferGetBlockNumber(buffer);
- freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData;
-
- UnlockReleaseBuffer(buffer);
-
- /* Remember first block number thus added. */
- if (firstBlock == InvalidBlockNumber)
- firstBlock = blockNum;
-
- /*
- * Immediately update the bottom level of the FSM. This has a good
- * chance of making this page visible to other concurrently inserting
- * backends, and we want that to happen without delay.
- */
- RecordPageWithFreeSpace(relation, blockNum, freespace);
+ extend_by_pages = 1;
+ }
+ else
+ {
+ /*
+ * Try to extend at least by the number of pages the caller needs. We
+ * can remember the additional pages (either via FSM or bistate).
+ */
+ extend_by_pages = num_pages;
+
+ if (!RELATION_IS_LOCAL(relation))
+ waitcount = RelationExtensionLockWaiterCount(relation);
+ else
+ waitcount = 0;
+
+ /*
+ * Multiply the number of pages to extend by the number of waiters. Do
+ * this even if we're not using the FSM, as it still relieves
+ * contention, by deferring the next time this backend needs to
+ * extend. In that case the extended pages will be found via
+ * bistate->next_free.
+ */
+ extend_by_pages += extend_by_pages * waitcount;
+
+ /*
+ * Can't extend by more than MAX_BUFFERS, we need to pin them all
+ * concurrently.
+ */
+ extend_by_pages = Min(extend_by_pages, MAX_BUFFERS_TO_EXTEND_BY);
}
- while (--extraBlocks > 0);
/*
- * Updating the upper levels of the free space map is too expensive to do
- * for every block, but it's worth doing once at the end to make sure that
- * subsequent insertion activity sees all of those nifty free pages we
- * just inserted.
+ * How many of the extended pages should be entered into the FSM?
+ *
+ * If we have a bistate, only enter pages that we don't need ourselves
+ * into the FSM. Otherwise every other backend will immediately try to
+ * use the pages this backend neds itself, causing unnecessary
+ * contention. If we don't have a bistate, we can't avoid the FSM.
+ *
+ * Never enter the page returned into the FSM, we'll immediately use it.
*/
- FreeSpaceMapVacuumRange(relation, firstBlock, blockNum + 1);
+ if (num_pages > 1 && bistate == NULL)
+ not_in_fsm_pages = 1;
+ else
+ not_in_fsm_pages = num_pages;
+
+ /* prepare to put another buffer into the bistate */
+ if (bistate && bistate->current_buf != InvalidBuffer)
+ {
+ ReleaseBuffer(bistate->current_buf);
+ bistate->current_buf = InvalidBuffer;
+ }
+
+ /*
+ * Extend the relation. We ask for the first returned page to be locked,
+ * so that we are sure that nobody has inserted into the page
+ * concurrently.
+ *
+ * With the current MAX_BUFFERS_TO_EXTEND_BY there's no danger of
+ * [auto]vacuum trying to truncate later pages as REL_TRUNCATE_MINIMUM is
+ * way larger.
+ */
+ firstBlock = ExtendBufferedRelBy(EB_REL(relation), MAIN_FORKNUM,
+ bistate ? bistate->strategy : NULL,
+ EB_LOCK_FIRST,
+ extend_by_pages,
+ victim_buffers,
+ &extend_by_pages);
+ /* the buffer the function will return */
+ buffer = victim_buffers[0];
+
+ /*
+ * Relation is now extended. Release pins on all buffers, except for the
+ * first (which we'll return). If we decided to put pages into the FSM,
+ * we can do that as part of the same loop.
+ *
+ * FIXME: Figure out how to better deal with doing this operation while
+ * holding a buffer lock. Likely we can just release the buffer lock (to
+ * reacquire it later) after initializing the page - the target page isn't
+ * in the FSM, so it's going to be very rare that it's found.
+ */
+ curBlock = firstBlock;
+ for (uint32 i = 0; i < extend_by_pages; i++, curBlock++)
+ {
+ Assert(curBlock == BufferGetBlockNumber(victim_buffers[i]));
+ Assert(BlockNumberIsValid(curBlock));
+
+ /* don't release the pin on the page returned by this function */
+ if (i > 0)
+ ReleaseBuffer(victim_buffers[i]);
+
+ if (i >= not_in_fsm_pages && use_fsm)
+ {
+ if (firstBlockFSM == InvalidBlockNumber)
+ firstBlockFSM = curBlock;
+
+ RecordPageWithFreeSpace(relation,
+ curBlock,
+ BufferGetPageSize(victim_buffers[i]) - SizeOfPageHeaderData);
+ }
+ }
+
+ if (use_fsm && firstBlockFSM != InvalidBlockNumber)
+ FreeSpaceMapVacuumRange(relation, firstBlockFSM, firstBlock + num_pages);
+
+ if (bistate)
+ {
+ /*
+ * Remember the pages we extended by so we can use them without
+ * looking into the FSM.
+ */
+ if (extend_by_pages > 1)
+ {
+ bistate->next_free = firstBlock + 1;
+ bistate->last_free = firstBlock + extend_by_pages - 1;
+ }
+ else
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+
+ /* maintain bistate->current_buf */
+ IncrBufferRefCount(buffer);
+ bistate->current_buf = buffer;
+ }
+
+ return buffer;
+#undef MAX_BUFFERS_TO_EXTEND_BY
}
+
/*
* RelationGetBufferForTuple
*
@@ -350,10 +437,12 @@ RelationGetBufferForTuple(Relation relation, Size len,
targetFreeSpace = 0;
BlockNumber targetBlock,
otherBlock;
- bool needLock;
len = MAXALIGN(len); /* be conservative */
+ if (num_pages <= 0)
+ num_pages = 1;
+
/* Bulk insert is not supported for updates, only inserts. */
Assert(otherBuffer == InvalidBuffer || !bistate);
@@ -558,83 +647,50 @@ loop:
ReleaseBuffer(buffer);
}
- /* Without FSM, always fall out of the loop and extend */
- if (!use_fsm)
- break;
-
- /*
- * Update FSM as to condition of this page, and ask for another page
- * to try.
- */
- targetBlock = RecordAndGetPageWithFreeSpace(relation,
- targetBlock,
- pageFreeSpace,
- targetFreeSpace);
- }
-
- /*
- * Have to extend the relation.
- *
- * We have to use a lock to ensure no one else is extending the rel at the
- * same time, else we will both try to initialize the same new page. We
- * can skip locking for new or temp relations, however, since no one else
- * could be accessing them.
- */
- needLock = !RELATION_IS_LOCAL(relation);
-
- /*
- * If we need the lock but are not able to acquire it immediately, we'll
- * consider extending the relation by multiple blocks at a time to manage
- * contention on the relation extension lock. However, this only makes
- * sense if we're using the FSM; otherwise, there's no point.
- */
- if (needLock)
- {
- if (!use_fsm)
- LockRelationForExtension(relation, ExclusiveLock);
- else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock))
+ if (bistate
+ && bistate->next_free != InvalidBlockNumber
+ && bistate->next_free <= bistate->last_free)
{
- /* Couldn't get the lock immediately; wait for it. */
- LockRelationForExtension(relation, ExclusiveLock);
-
/*
- * Check if some other backend has extended a block for us while
- * we were waiting on the lock.
+ * We bulk extended the relation before, and there are still some
+ * unused pages from that extension, so we don't need to look in
+ * the FSM for a new page. But do record the free space from the
+ * last page, somebody might insert narrower tuples later.
*/
- targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
+ if (use_fsm)
+ RecordPageWithFreeSpace(relation, targetBlock, pageFreeSpace);
- /*
- * If some other waiter has already extended the relation, we
- * don't need to do so; just use the existing freespace.
- */
- if (targetBlock != InvalidBlockNumber)
+ Assert(bistate->last_free != InvalidBlockNumber &&
+ bistate->next_free <= bistate->last_free);
+ targetBlock = bistate->next_free;
+ if (bistate->next_free >= bistate->last_free)
{
- UnlockRelationForExtension(relation, ExclusiveLock);
- goto loop;
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
}
-
- /* Time to bulk-extend. */
- RelationAddExtraBlocks(relation, bistate);
+ else
+ bistate->next_free++;
+ }
+ else if (!use_fsm)
+ {
+ /* Without FSM, always fall out of the loop and extend */
+ break;
+ }
+ else
+ {
+ /*
+ * Update FSM as to condition of this page, and ask for another
+ * page to try.
+ */
+ targetBlock = RecordAndGetPageWithFreeSpace(relation,
+ targetBlock,
+ pageFreeSpace,
+ targetFreeSpace);
}
}
- /*
- * In addition to whatever extension we performed above, we always add at
- * least one block to satisfy our own request.
- *
- * XXX This does an lseek - rather expensive - but at the moment it is the
- * only way to accurately determine how many blocks are in a relation. Is
- * it worth keeping an accurate file length in shared memory someplace,
- * rather than relying on the kernel to do it for us?
- */
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
-
- /*
- * Release the file-extension lock; it's now OK for someone else to extend
- * the relation some more.
- */
- if (needLock)
- UnlockRelationForExtension(relation, ExclusiveLock);
+ /* Have to extend the relation */
+ buffer = RelationAddBlocks(relation, bistate, num_pages, use_fsm);
/*
* We need to initialize the empty new page. Double-check that it really
--
2.38.0
--wwosng5vofi3dmcs
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v5-0012-WIP-Don-t-initialize-page-in-vm-fsm-_extend-not-n.patch"
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v7 12/14] hio: Use ExtendBufferedRelBy()
@ 2023-03-29 01:39 Andres Freund <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Andres Freund @ 2023-03-29 01:39 UTC (permalink / raw)
Reviewed-by: Melanie Plageman <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
src/backend/access/heap/hio.c | 372 ++++++++++++++++++++--------------
1 file changed, 218 insertions(+), 154 deletions(-)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 561d7329058..574c56cfb5f 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -212,87 +212,197 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
}
/*
- * Extend a relation by multiple blocks to avoid future contention on the
- * relation extension lock. Our goal is to pre-extend the relation by an
- * amount which ramps up as the degree of contention ramps up, but limiting
- * the result to some sane overall value.
+ * Extend the relation. By multiple pages, if beneficial.
+ *
+ * If the caller needs multiple pages (num_pages > 1), we always try to extend
+ * by at least that much.
+ *
+ * If there is contention on the extension lock, we don't just extend "for
+ * ourselves", but we try to help others. We can do so by adding empty pages
+ * into the FSM. Typically there is no contention when we can't use the FSM.
+ *
+ * We do have to limit the number of pages to extend by to some value, as the
+ * buffers for all the extended pages need to, temporarily, be pinned. For now
+ * we define MAX_BUFFERS_TO_EXTEND_BY to be 64 buffers, it's hard to see
+ * benefits with higher numbers. This partially is because copyfrom.c's
+ * MAX_BUFFERED_TUPLES / MAX_BUFFERED_BYTES prevents larger multi_inserts.
*/
-static void
-RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
+static Buffer
+RelationAddBlocks(Relation relation, BulkInsertState bistate,
+ int num_pages, bool use_fsm, bool *did_unlock)
{
- BlockNumber blockNum,
- firstBlock = InvalidBlockNumber;
- int extraBlocks;
- int lockWaiters;
-
- /* Use the length of the lock wait queue to judge how much to extend. */
- lockWaiters = RelationExtensionLockWaiterCount(relation);
- if (lockWaiters <= 0)
- return;
+#define MAX_BUFFERS_TO_EXTEND_BY 64
+ Buffer victim_buffers[MAX_BUFFERS_TO_EXTEND_BY];
+ BlockNumber first_block = InvalidBlockNumber;
+ BlockNumber last_block = InvalidBlockNumber;
+ uint32 extend_by_pages;
+ uint32 not_in_fsm_pages;
+ Buffer buffer;
+ Page page;
/*
- * It might seem like multiplying the number of lock waiters by as much as
- * 20 is too aggressive, but benchmarking revealed that smaller numbers
- * were insufficient. 512 is just an arbitrary cap to prevent
- * pathological results.
+ * Determine by how many pages to try to extend by.
*/
- extraBlocks = Min(512, lockWaiters * 20);
-
- do
+ if (bistate == NULL && !use_fsm)
{
- Buffer buffer;
- Page page;
- Size freespace;
-
/*
- * Extend by one page. This should generally match the main-line
- * extension code in RelationGetBufferForTuple, except that we hold
- * the relation extension lock throughout, and we don't immediately
- * initialize the page (see below).
+ * If we have neither bistate, nor can use the FSM, we can't bulk
+ * extend - there'd be no way to find the additional pages.
*/
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
- page = BufferGetPage(buffer);
-
- if (!PageIsNew(page))
- elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- BufferGetBlockNumber(buffer),
- RelationGetRelationName(relation));
-
- /*
- * Add the page to the FSM without initializing. If we were to
- * initialize here, the page would potentially get flushed out to disk
- * before we add any useful content. There's no guarantee that that'd
- * happen before a potential crash, so we need to deal with
- * uninitialized pages anyway, thus avoid the potential for
- * unnecessary writes.
- */
-
- /* we'll need this info below */
- blockNum = BufferGetBlockNumber(buffer);
- freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData;
-
- UnlockReleaseBuffer(buffer);
-
- /* Remember first block number thus added. */
- if (firstBlock == InvalidBlockNumber)
- firstBlock = blockNum;
-
- /*
- * Immediately update the bottom level of the FSM. This has a good
- * chance of making this page visible to other concurrently inserting
- * backends, and we want that to happen without delay.
- */
- RecordPageWithFreeSpace(relation, blockNum, freespace);
+ extend_by_pages = 1;
+ }
+ else
+ {
+ uint32 waitcount;
+
+ /*
+ * Try to extend at least by the number of pages the caller needs. We
+ * can remember the additional pages (either via FSM or bistate).
+ */
+ extend_by_pages = num_pages;
+
+ if (!RELATION_IS_LOCAL(relation))
+ waitcount = RelationExtensionLockWaiterCount(relation);
+ else
+ waitcount = 0;
+
+ /*
+ * Multiply the number of pages to extend by the number of waiters. Do
+ * this even if we're not using the FSM, as it still relieves
+ * contention, by deferring the next time this backend needs to
+ * extend. In that case the extended pages will be found via
+ * bistate->next_free.
+ */
+ extend_by_pages += extend_by_pages * waitcount;
+
+ /*
+ * Can't extend by more than MAX_BUFFERS_TO_EXTEND_BY, we need to pin
+ * them all concurrently.
+ */
+ extend_by_pages = Min(extend_by_pages, MAX_BUFFERS_TO_EXTEND_BY);
}
- while (--extraBlocks > 0);
/*
- * Updating the upper levels of the free space map is too expensive to do
- * for every block, but it's worth doing once at the end to make sure that
- * subsequent insertion activity sees all of those nifty free pages we
- * just inserted.
+ * How many of the extended pages should be entered into the FSM?
+ *
+ * If we have a bistate, only enter pages that we don't need ourselves
+ * into the FSM. Otherwise every other backend will immediately try to
+ * use the pages this backend needs for itself, causing unnecessary
+ * contention. If we don't have a bistate, we can't avoid the FSM.
+ *
+ * Never enter the page returned into the FSM, we'll immediately use it.
*/
- FreeSpaceMapVacuumRange(relation, firstBlock, blockNum + 1);
+ if (num_pages > 1 && bistate == NULL)
+ not_in_fsm_pages = 1;
+ else
+ not_in_fsm_pages = num_pages;
+
+ /* prepare to put another buffer into the bistate */
+ if (bistate && bistate->current_buf != InvalidBuffer)
+ {
+ ReleaseBuffer(bistate->current_buf);
+ bistate->current_buf = InvalidBuffer;
+ }
+
+ /*
+ * Extend the relation. We ask for the first returned page to be locked,
+ * so that we are sure that nobody has inserted into the page
+ * concurrently.
+ *
+ * With the current MAX_BUFFERS_TO_EXTEND_BY there's no danger of
+ * [auto]vacuum trying to truncate later pages as REL_TRUNCATE_MINIMUM is
+ * way larger.
+ */
+ first_block = ExtendBufferedRelBy(EB_REL(relation), MAIN_FORKNUM,
+ bistate ? bistate->strategy : NULL,
+ EB_LOCK_FIRST,
+ extend_by_pages,
+ victim_buffers,
+ &extend_by_pages);
+ buffer = victim_buffers[0]; /* the buffer the function will return */
+ last_block = first_block + (extend_by_pages - 1);
+ Assert(first_block == BufferGetBlockNumber(buffer));
+
+ /*
+ * Relation is now extended. Initialize the page. We do this here, before
+ * potentially releasing the lock on the page, because it allows us to
+ * double check that the page contents are empty (this should never
+ * happen, but if it does we don't want to risk wiping out valid data).
+ */
+ page = BufferGetPage(buffer);
+ if (!PageIsNew(page))
+ elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
+ first_block,
+ RelationGetRelationName(relation));
+
+ PageInit(page, BufferGetPageSize(buffer), 0);
+ MarkBufferDirty(buffer);
+
+ /*
+ * If we decided to put pages into the FSM, release the buffer lock (but
+ * not pin), we don't want to do IO while holding a buffer lock. This will
+ * necessitate a bit more extensive checking in our caller.
+ */
+ if (use_fsm && not_in_fsm_pages < extend_by_pages)
+ {
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+ *did_unlock = true;
+ }
+
+ /*
+ * Relation is now extended. Release pins on all buffers, except for the
+ * first (which we'll return). If we decided to put pages into the FSM,
+ * we can do that as part of the same loop.
+ */
+ for (uint32 i = 1; i < extend_by_pages; i++)
+ {
+ BlockNumber curBlock = first_block + i;
+
+ Assert(curBlock == BufferGetBlockNumber(victim_buffers[i]));
+ Assert(BlockNumberIsValid(curBlock));
+
+ ReleaseBuffer(victim_buffers[i]);
+
+ if (use_fsm && i >= not_in_fsm_pages)
+ {
+ Size freespace = BufferGetPageSize(victim_buffers[i]) -
+ SizeOfPageHeaderData;
+
+ RecordPageWithFreeSpace(relation, curBlock, freespace);
+ }
+ }
+
+ if (use_fsm && not_in_fsm_pages < extend_by_pages)
+ {
+ BlockNumber first_fsm_block = first_block + not_in_fsm_pages;
+
+ FreeSpaceMapVacuumRange(relation, first_fsm_block, last_block);
+ }
+
+ if (bistate)
+ {
+ /*
+ * Remember the additionaly pages we extended by, so we later can use
+ * them without looking into the FSM.
+ */
+ if (extend_by_pages > 1)
+ {
+ bistate->next_free = first_block + 1;
+ bistate->last_free = last_block;
+ }
+ else
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+
+ /* maintain bistate->current_buf */
+ IncrBufferRefCount(buffer);
+ bistate->current_buf = buffer;
+ }
+
+ return buffer;
+#undef MAX_BUFFERS_TO_EXTEND_BY
}
/*
@@ -376,12 +486,14 @@ RelationGetBufferForTuple(Relation relation, Size len,
targetFreeSpace = 0;
BlockNumber targetBlock,
otherBlock;
- bool needLock;
bool unlockedTargetBuffer;
bool recheckVmPins;
len = MAXALIGN(len); /* be conservative */
+ if (num_pages <= 0)
+ num_pages = 1;
+
/* Bulk insert is not supported for updates, only inserts. */
Assert(otherBuffer == InvalidBuffer || !bistate);
@@ -581,102 +693,54 @@ loop:
ReleaseBuffer(buffer);
}
- /* Without FSM, always fall out of the loop and extend */
- if (!use_fsm)
- break;
-
- /*
- * Update FSM as to condition of this page, and ask for another page
- * to try.
- */
- targetBlock = RecordAndGetPageWithFreeSpace(relation,
- targetBlock,
- pageFreeSpace,
- targetFreeSpace);
- }
-
- /*
- * Have to extend the relation.
- *
- * We have to use a lock to ensure no one else is extending the rel at the
- * same time, else we will both try to initialize the same new page. We
- * can skip locking for new or temp relations, however, since no one else
- * could be accessing them.
- */
- needLock = !RELATION_IS_LOCAL(relation);
-
- /*
- * If we need the lock but are not able to acquire it immediately, we'll
- * consider extending the relation by multiple blocks at a time to manage
- * contention on the relation extension lock. However, this only makes
- * sense if we're using the FSM; otherwise, there's no point.
- */
- if (needLock)
- {
- if (!use_fsm)
- LockRelationForExtension(relation, ExclusiveLock);
- else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock))
+ if (bistate && bistate->next_free != InvalidBlockNumber)
{
- /* Couldn't get the lock immediately; wait for it. */
- LockRelationForExtension(relation, ExclusiveLock);
+ Assert(bistate->next_free <= bistate->last_free);
/*
- * Check if some other backend has extended a block for us while
- * we were waiting on the lock.
+ * We bulk extended the relation before, and there are still some
+ * unused pages from that extension, so we don't need to look in
+ * the FSM for a new page. But do record the free space from the
+ * last page, somebody might insert narrower tuples later.
*/
- targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
+ if (use_fsm)
+ RecordPageWithFreeSpace(relation, targetBlock, pageFreeSpace);
- /*
- * If some other waiter has already extended the relation, we
- * don't need to do so; just use the existing freespace.
- */
- if (targetBlock != InvalidBlockNumber)
+ targetBlock = bistate->next_free;
+ if (bistate->next_free >= bistate->last_free)
{
- UnlockRelationForExtension(relation, ExclusiveLock);
- goto loop;
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
}
-
- /* Time to bulk-extend. */
- RelationAddExtraBlocks(relation, bistate);
+ else
+ bistate->next_free++;
+ }
+ else if (!use_fsm)
+ {
+ /* Without FSM, always fall out of the loop and extend */
+ break;
+ }
+ else
+ {
+ /*
+ * Update FSM as to condition of this page, and ask for another
+ * page to try.
+ */
+ targetBlock = RecordAndGetPageWithFreeSpace(relation,
+ targetBlock,
+ pageFreeSpace,
+ targetFreeSpace);
}
}
- /*
- * In addition to whatever extension we performed above, we always add at
- * least one block to satisfy our own request.
- *
- * XXX This does an lseek - rather expensive - but at the moment it is the
- * only way to accurately determine how many blocks are in a relation. Is
- * it worth keeping an accurate file length in shared memory someplace,
- * rather than relying on the kernel to do it for us?
- */
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
-
- /*
- * Release the file-extension lock; it's now OK for someone else to extend
- * the relation some more.
- */
- if (needLock)
- UnlockRelationForExtension(relation, ExclusiveLock);
-
+ /* Have to extend the relation */
unlockedTargetBuffer = false;
+ buffer = RelationAddBlocks(relation, bistate, num_pages, use_fsm, &unlockedTargetBuffer);
+ recheckVmPins = unlockedTargetBuffer;
+
targetBlock = BufferGetBlockNumber(buffer);
-
- /*
- * We need to initialize the empty new page. Double-check that it really
- * is empty (this should never happen, but if it does we don't want to
- * risk wiping out valid data).
- */
page = BufferGetPage(buffer);
- if (!PageIsNew(page))
- elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- targetBlock,
- RelationGetRelationName(relation));
-
- PageInit(page, BufferGetPageSize(buffer), 0);
- MarkBufferDirty(buffer);
-
/*
* The page is empty, pin vmbuffer to set all_frozen bit. We don't want to
* do IO while the buffer is locked, so we unlock the page first if IO is
@@ -688,8 +752,9 @@ loop:
if (!visibilitymap_pin_ok(targetBlock, *vmbuffer))
{
+ if (!unlockedTargetBuffer)
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
unlockedTargetBuffer = true;
- LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, targetBlock, vmbuffer);
}
}
@@ -702,7 +767,6 @@ loop:
* that another backend used space on this page. We check for that below,
* and retry if necessary.
*/
- recheckVmPins = false;
if (unlockedTargetBuffer)
{
/* released lock on target buffer above */
--
2.38.0
--ju7ntqqtbf66a3ug
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v7-0013-Convert-a-few-places-to-ExtendBufferedRelTo.patch"
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v6 14/17] hio: Use ExtendBufferedRelBy()
@ 2023-03-29 01:39 Andres Freund <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Andres Freund @ 2023-03-29 01:39 UTC (permalink / raw)
---
src/backend/access/heap/hio.c | 370 ++++++++++++++++++++--------------
1 file changed, 217 insertions(+), 153 deletions(-)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index cc913a028e0..6d66826d951 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -192,87 +192,197 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
}
/*
- * Extend a relation by multiple blocks to avoid future contention on the
- * relation extension lock. Our goal is to pre-extend the relation by an
- * amount which ramps up as the degree of contention ramps up, but limiting
- * the result to some sane overall value.
+ * Extend the relation. By multiple pages, if beneficial.
+ *
+ * If the caller needs multiple pages (num_pages > 1), we always try to extend
+ * by at least that much.
+ *
+ * If there is contention on the extension lock, we don't just extend "for
+ * ourselves", but we try to help others. We can do so by adding empty pages
+ * into the FSM. Typically there is no contention when we can't use the FSM.
+ *
+ * We do have to limit the number of pages to extend by to some value, as the
+ * buffers for all the extended pages need to, temporarily, be pinned. For now
+ * we define MAX_BUFFERS_TO_EXTEND_BY to be 64 buffers, it's hard to see
+ * benefits with higher numbers. This partially is because copyfrom.c's
+ * MAX_BUFFERED_TUPLES / MAX_BUFFERED_BYTES prevents larger multi_inserts.
*/
-static void
-RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
+static Buffer
+RelationAddBlocks(Relation relation, BulkInsertState bistate,
+ int num_pages, bool use_fsm, bool *did_unlock)
{
- BlockNumber blockNum,
- firstBlock = InvalidBlockNumber;
- int extraBlocks;
- int lockWaiters;
-
- /* Use the length of the lock wait queue to judge how much to extend. */
- lockWaiters = RelationExtensionLockWaiterCount(relation);
- if (lockWaiters <= 0)
- return;
+#define MAX_BUFFERS_TO_EXTEND_BY 64
+ Buffer victim_buffers[MAX_BUFFERS_TO_EXTEND_BY];
+ BlockNumber first_block = InvalidBlockNumber;
+ BlockNumber last_block = InvalidBlockNumber;
+ uint32 extend_by_pages;
+ uint32 not_in_fsm_pages;
+ Buffer buffer;
+ Page page;
/*
- * It might seem like multiplying the number of lock waiters by as much as
- * 20 is too aggressive, but benchmarking revealed that smaller numbers
- * were insufficient. 512 is just an arbitrary cap to prevent
- * pathological results.
+ * Determine by how many pages to try to extend by.
*/
- extraBlocks = Min(512, lockWaiters * 20);
-
- do
+ if (bistate == NULL && !use_fsm)
{
- Buffer buffer;
- Page page;
- Size freespace;
-
/*
- * Extend by one page. This should generally match the main-line
- * extension code in RelationGetBufferForTuple, except that we hold
- * the relation extension lock throughout, and we don't immediately
- * initialize the page (see below).
+ * If we have neither bistate, nor can use the FSM, we can't bulk
+ * extend - there'd be no way to find the additional pages.
*/
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
- page = BufferGetPage(buffer);
-
- if (!PageIsNew(page))
- elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- BufferGetBlockNumber(buffer),
- RelationGetRelationName(relation));
-
- /*
- * Add the page to the FSM without initializing. If we were to
- * initialize here, the page would potentially get flushed out to disk
- * before we add any useful content. There's no guarantee that that'd
- * happen before a potential crash, so we need to deal with
- * uninitialized pages anyway, thus avoid the potential for
- * unnecessary writes.
- */
-
- /* we'll need this info below */
- blockNum = BufferGetBlockNumber(buffer);
- freespace = BufferGetPageSize(buffer) - SizeOfPageHeaderData;
-
- UnlockReleaseBuffer(buffer);
-
- /* Remember first block number thus added. */
- if (firstBlock == InvalidBlockNumber)
- firstBlock = blockNum;
-
- /*
- * Immediately update the bottom level of the FSM. This has a good
- * chance of making this page visible to other concurrently inserting
- * backends, and we want that to happen without delay.
- */
- RecordPageWithFreeSpace(relation, blockNum, freespace);
+ extend_by_pages = 1;
+ }
+ else
+ {
+ uint32 waitcount;
+
+ /*
+ * Try to extend at least by the number of pages the caller needs. We
+ * can remember the additional pages (either via FSM or bistate).
+ */
+ extend_by_pages = num_pages;
+
+ if (!RELATION_IS_LOCAL(relation))
+ waitcount = RelationExtensionLockWaiterCount(relation);
+ else
+ waitcount = 0;
+
+ /*
+ * Multiply the number of pages to extend by the number of waiters. Do
+ * this even if we're not using the FSM, as it still relieves
+ * contention, by deferring the next time this backend needs to
+ * extend. In that case the extended pages will be found via
+ * bistate->next_free.
+ */
+ extend_by_pages += extend_by_pages * waitcount;
+
+ /*
+ * Can't extend by more than MAX_BUFFERS_TO_EXTEND_BY, we need to pin
+ * them all concurrently.
+ */
+ extend_by_pages = Min(extend_by_pages, MAX_BUFFERS_TO_EXTEND_BY);
}
- while (--extraBlocks > 0);
/*
- * Updating the upper levels of the free space map is too expensive to do
- * for every block, but it's worth doing once at the end to make sure that
- * subsequent insertion activity sees all of those nifty free pages we
- * just inserted.
+ * How many of the extended pages should be entered into the FSM?
+ *
+ * If we have a bistate, only enter pages that we don't need ourselves
+ * into the FSM. Otherwise every other backend will immediately try to
+ * use the pages this backend needs for itself, causing unnecessary
+ * contention. If we don't have a bistate, we can't avoid the FSM.
+ *
+ * Never enter the page returned into the FSM, we'll immediately use it.
*/
- FreeSpaceMapVacuumRange(relation, firstBlock, blockNum + 1);
+ if (num_pages > 1 && bistate == NULL)
+ not_in_fsm_pages = 1;
+ else
+ not_in_fsm_pages = num_pages;
+
+ /* prepare to put another buffer into the bistate */
+ if (bistate && bistate->current_buf != InvalidBuffer)
+ {
+ ReleaseBuffer(bistate->current_buf);
+ bistate->current_buf = InvalidBuffer;
+ }
+
+ /*
+ * Extend the relation. We ask for the first returned page to be locked,
+ * so that we are sure that nobody has inserted into the page
+ * concurrently.
+ *
+ * With the current MAX_BUFFERS_TO_EXTEND_BY there's no danger of
+ * [auto]vacuum trying to truncate later pages as REL_TRUNCATE_MINIMUM is
+ * way larger.
+ */
+ first_block = ExtendBufferedRelBy(EB_REL(relation), MAIN_FORKNUM,
+ bistate ? bistate->strategy : NULL,
+ EB_LOCK_FIRST,
+ extend_by_pages,
+ victim_buffers,
+ &extend_by_pages);
+ buffer = victim_buffers[0]; /* the buffer the function will return */
+ last_block = first_block + (extend_by_pages - 1);
+ Assert(first_block == BufferGetBlockNumber(buffer));
+
+ /*
+ * Relation is now extended. Initialize the page. We do this here, before
+ * potentially releasing the lock on the page, because it allows us to
+ * double check that the page contents are empty (this should never
+ * happen, but if it does we don't want to risk wiping out valid data).
+ */
+ page = BufferGetPage(buffer);
+ if (!PageIsNew(page))
+ elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
+ first_block,
+ RelationGetRelationName(relation));
+
+ PageInit(page, BufferGetPageSize(buffer), 0);
+ MarkBufferDirty(buffer);
+
+ /*
+ * If we decided to put pages into the FSM, release the buffer lock (but
+ * not pin), we don't want to do IO while holding a buffer lock. This will
+ * necessitate a bit more extensive checking in our caller.
+ */
+ if (use_fsm && not_in_fsm_pages < extend_by_pages)
+ {
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+ *did_unlock = true;
+ }
+
+ /*
+ * Relation is now extended. Release pins on all buffers, except for the
+ * first (which we'll return). If we decided to put pages into the FSM,
+ * we can do that as part of the same loop.
+ */
+ for (uint32 i = 1; i < extend_by_pages; i++)
+ {
+ BlockNumber curBlock = first_block + i;
+
+ Assert(curBlock == BufferGetBlockNumber(victim_buffers[i]));
+ Assert(BlockNumberIsValid(curBlock));
+
+ ReleaseBuffer(victim_buffers[i]);
+
+ if (use_fsm && i >= not_in_fsm_pages)
+ {
+ Size freespace = BufferGetPageSize(victim_buffers[i]) -
+ SizeOfPageHeaderData;
+
+ RecordPageWithFreeSpace(relation, curBlock, freespace);
+ }
+ }
+
+ if (use_fsm && not_in_fsm_pages < extend_by_pages)
+ {
+ BlockNumber first_fsm_block = first_block + not_in_fsm_pages;
+
+ FreeSpaceMapVacuumRange(relation, first_fsm_block, last_block);
+ }
+
+ if (bistate)
+ {
+ /*
+ * Remember the additionaly pages we extended by, so we later can use
+ * them without looking into the FSM.
+ */
+ if (extend_by_pages > 1)
+ {
+ bistate->next_free = first_block + 1;
+ bistate->last_free = last_block;
+ }
+ else
+ {
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
+ }
+
+ /* maintain bistate->current_buf */
+ IncrBufferRefCount(buffer);
+ bistate->current_buf = buffer;
+ }
+
+ return buffer;
+#undef MAX_BUFFERS_TO_EXTEND_BY
}
/*
@@ -356,11 +466,13 @@ RelationGetBufferForTuple(Relation relation, Size len,
targetFreeSpace = 0;
BlockNumber targetBlock,
otherBlock;
- bool needLock;
bool unlockedTargetBuffer;
len = MAXALIGN(len); /* be conservative */
+ if (num_pages <= 0)
+ num_pages = 1;
+
/* Bulk insert is not supported for updates, only inserts. */
Assert(otherBuffer == InvalidBuffer || !bistate);
@@ -565,102 +677,53 @@ loop:
ReleaseBuffer(buffer);
}
- /* Without FSM, always fall out of the loop and extend */
- if (!use_fsm)
- break;
-
- /*
- * Update FSM as to condition of this page, and ask for another page
- * to try.
- */
- targetBlock = RecordAndGetPageWithFreeSpace(relation,
- targetBlock,
- pageFreeSpace,
- targetFreeSpace);
- }
-
- /*
- * Have to extend the relation.
- *
- * We have to use a lock to ensure no one else is extending the rel at the
- * same time, else we will both try to initialize the same new page. We
- * can skip locking for new or temp relations, however, since no one else
- * could be accessing them.
- */
- needLock = !RELATION_IS_LOCAL(relation);
-
- /*
- * If we need the lock but are not able to acquire it immediately, we'll
- * consider extending the relation by multiple blocks at a time to manage
- * contention on the relation extension lock. However, this only makes
- * sense if we're using the FSM; otherwise, there's no point.
- */
- if (needLock)
- {
- if (!use_fsm)
- LockRelationForExtension(relation, ExclusiveLock);
- else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock))
+ if (bistate && bistate->next_free != InvalidBlockNumber)
{
- /* Couldn't get the lock immediately; wait for it. */
- LockRelationForExtension(relation, ExclusiveLock);
+ Assert(bistate->next_free <= bistate->last_free);
/*
- * Check if some other backend has extended a block for us while
- * we were waiting on the lock.
+ * We bulk extended the relation before, and there are still some
+ * unused pages from that extension, so we don't need to look in
+ * the FSM for a new page. But do record the free space from the
+ * last page, somebody might insert narrower tuples later.
*/
- targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
+ if (use_fsm)
+ RecordPageWithFreeSpace(relation, targetBlock, pageFreeSpace);
- /*
- * If some other waiter has already extended the relation, we
- * don't need to do so; just use the existing freespace.
- */
- if (targetBlock != InvalidBlockNumber)
+ targetBlock = bistate->next_free;
+ if (bistate->next_free >= bistate->last_free)
{
- UnlockRelationForExtension(relation, ExclusiveLock);
- goto loop;
+ bistate->next_free = InvalidBlockNumber;
+ bistate->last_free = InvalidBlockNumber;
}
-
- /* Time to bulk-extend. */
- RelationAddExtraBlocks(relation, bistate);
+ else
+ bistate->next_free++;
+ }
+ else if (!use_fsm)
+ {
+ /* Without FSM, always fall out of the loop and extend */
+ break;
+ }
+ else
+ {
+ /*
+ * Update FSM as to condition of this page, and ask for another
+ * page to try.
+ */
+ targetBlock = RecordAndGetPageWithFreeSpace(relation,
+ targetBlock,
+ pageFreeSpace,
+ targetFreeSpace);
}
}
- /*
- * In addition to whatever extension we performed above, we always add at
- * least one block to satisfy our own request.
- *
- * XXX This does an lseek - rather expensive - but at the moment it is the
- * only way to accurately determine how many blocks are in a relation. Is
- * it worth keeping an accurate file length in shared memory someplace,
- * rather than relying on the kernel to do it for us?
- */
- buffer = ReadBufferBI(relation, P_NEW, RBM_ZERO_AND_LOCK, bistate);
-
- /*
- * Release the file-extension lock; it's now OK for someone else to extend
- * the relation some more.
- */
- if (needLock)
- UnlockRelationForExtension(relation, ExclusiveLock);
-
+ /* Have to extend the relation */
unlockedTargetBuffer = false;
+ buffer = RelationAddBlocks(relation, bistate, num_pages, use_fsm, &unlockedTargetBuffer);
+
targetBlock = BufferGetBlockNumber(buffer);
-
- /*
- * We need to initialize the empty new page. Double-check that it really
- * is empty (this should never happen, but if it does we don't want to
- * risk wiping out valid data).
- */
page = BufferGetPage(buffer);
- if (!PageIsNew(page))
- elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- targetBlock,
- RelationGetRelationName(relation));
-
- PageInit(page, BufferGetPageSize(buffer), 0);
- MarkBufferDirty(buffer);
-
/*
* The page is empty, pin vmbuffer to set all_frozen bit. We don't want to
* do IO while the buffer is locked, so we unlock the page first if IO is
@@ -672,8 +735,9 @@ loop:
if (!visibilitymap_pin_ok(targetBlock, *vmbuffer))
{
+ if (!unlockedTargetBuffer)
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
unlockedTargetBuffer = true;
- LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
visibilitymap_pin(relation, targetBlock, vmbuffer);
}
}
--
2.38.0
--cfn72vqnlbycypta
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v6-0015-WIP-Don-t-initialize-page-in-vm-fsm-_extend-not-n.patch"
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v2 2/7] Row pattern recognition patch (parse/analysis).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)
---
src/backend/parser/parse_agg.c | 7 ++
src/backend/parser/parse_clause.c | 192 +++++++++++++++++++++++++++++-
src/backend/parser/parse_expr.c | 4 +
src/backend/parser/parse_func.c | 3 +
4 files changed, 205 insertions(+), 1 deletion(-)
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 85cd47b7ae..aa7a1cee80 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -564,6 +564,10 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
errkind = true;
break;
+ case EXPR_KIND_RPR_DEFINE:
+ errkind = true;
+ break;
+
/*
* There is intentionally no default: case here, so that the
* compiler will warn if we add a new ParseExprKind without
@@ -953,6 +957,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_RPR_DEFINE:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index f61f794755..ccf3332bef 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -100,7 +100,10 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
Node *clause);
-
+static void transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static List *transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static List *transformPatternClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
+static List *transformMeasureClause(ParseState *pstate, WindowClause *wc, WindowDef *windef);
/*
* transformFromClause -
@@ -2949,6 +2952,10 @@ transformWindowDefinitions(ParseState *pstate,
rangeopfamily, rangeopcintype,
&wc->endInRangeFunc,
windef->endOffset);
+
+ /* Process Row Pattern Recognition related clauses */
+ transformRPR(pstate, wc, windef);
+
wc->runCondition = NIL;
wc->winref = winref;
@@ -3814,3 +3821,186 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
return node;
}
+
+/*
+ * transformRPR
+ * Process Row Pattern Recognition related clauses
+ */
+static void
+transformRPR(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+ /* Check Frame option. Frame must start at current row */
+
+ /*
+ * Window definition exists?
+ */
+ if (windef == NULL)
+ return;
+
+ /*
+ * Row Pattern Common Syntax clause exists?
+ */
+ if (windef->rpCommonSyntax == NULL)
+ return;
+
+ if ((wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("FRAME must start at current row when row patttern recognition is used")));
+
+ /* Transform AFTER MACH SKIP TO clause */
+ wc->rpSkipTo = windef->rpCommonSyntax->rpSkipTo;
+
+ /* Transform SEEK or INITIAL clause */
+ wc->initial = windef->rpCommonSyntax->initial;
+
+ /* Transform DEFINE clause into list of TargetEntry's */
+ wc->defineClause = transformDefineClause(pstate, wc, windef);
+
+ /* Check PATTERN clause and copy to patternClause */
+ transformPatternClause(pstate, wc, windef);
+
+ /* Transform MEASURE clause */
+ transformMeasureClause(pstate, wc, windef);
+}
+
+/*
+ * transformDefineClause Process DEFINE clause and transform ResTarget into
+ * list of TargetEntry.
+ *
+ * XXX we only support column reference in row pattern definition search
+ * condition, e.g. "price". <row pattern definition variable name>.<column
+ * reference> is not supported, e.g. "A.price".
+ */
+static List *
+transformDefineClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+ ListCell *lc;
+ ResTarget *restarget, *r;
+ List *restargets;
+
+
+ /*
+ * If Row Definition Common Syntax exists, DEFINE clause must exist.
+ * (the raw parser should have already checked it.)
+ */
+ Assert(windef->rpCommonSyntax->rpDefs != NULL);
+
+ /*
+ * Check for duplicate row pattern definition variables. The standard
+ * requires that no two row pattern definition variable names shall be
+ * equivalent.
+ */
+ restargets = NIL;
+ foreach(lc, windef->rpCommonSyntax->rpDefs)
+ {
+ char *name;
+ ListCell *l;
+
+ restarget = (ResTarget *)lfirst(lc);
+ name = restarget->name;
+
+ /*
+ * Make sure that row pattern definition search condition is a boolean
+ * expression.
+ */
+ transformWhereClause(pstate, restarget->val,
+ EXPR_KIND_RPR_DEFINE, "DEFINE");
+
+ foreach(l, restargets)
+ {
+ char *n;
+
+ r = (ResTarget *) lfirst(l);
+ n = r->name;
+
+ if (!strcmp(n, name))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("row pattern definition variable name \"%s\" appears more than once in DEFINE clause",
+ name),
+ parser_errposition(pstate, exprLocation((Node *)r))));
+ }
+ restargets = lappend(restargets, restarget);
+ }
+ list_free(restargets);
+
+ return transformTargetList(pstate, windef->rpCommonSyntax->rpDefs,
+ EXPR_KIND_RPR_DEFINE);
+}
+
+/*
+ * transformPatternClause
+ * Process PATTERN clause and return PATTERN clause in the raw parse tree
+ */
+static List *
+transformPatternClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+ List *patterns;
+ ListCell *lc, *l;
+
+ /*
+ * Row Pattern Common Syntax clause exists?
+ */
+ if (windef->rpCommonSyntax == NULL)
+ return NULL;
+
+ /*
+ * Primary row pattern variable names in PATTERN clause must appear in
+ * DEFINE clause as row pattern definition variable names.
+ */
+ wc->patternVariable = NIL;
+ wc->patternRegexp = NIL;
+ foreach(lc, windef->rpCommonSyntax->rpPatterns)
+ {
+ A_Expr *a;
+ char *name;
+ char *regexp;
+ bool found = false;
+
+ if (!IsA(lfirst(lc), A_Expr))
+ ereport(ERROR,
+ errmsg("node type is not A_Expr"));
+
+ a = (A_Expr *)lfirst(lc);
+ name = strVal(a->lexpr);
+
+ foreach(l, windef->rpCommonSyntax->rpDefs)
+ {
+ ResTarget *restarget = (ResTarget *)lfirst(l);
+
+ if (!strcmp(restarget->name, name))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("primary row pattern variable name \"%s\" does not appear in DEFINE clause",
+ name),
+ parser_errposition(pstate, exprLocation((Node *)a))));
+ wc->patternVariable = lappend(wc->patternVariable, makeString(pstrdup(name)));
+ regexp = strVal(lfirst(list_head(a->name)));
+ wc->patternRegexp = lappend(wc->patternRegexp, makeString(pstrdup(regexp)));
+ }
+ return patterns;
+}
+
+/*
+ * transformMeasureClause
+ * Process MEASURE clause
+ */
+static List *
+transformMeasureClause(ParseState *pstate, WindowClause *wc, WindowDef *windef)
+{
+ if (windef->rowPatternMeasures == NIL)
+ return NIL;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("%s","MEASURE clause is not supported yet"),
+ parser_errposition(pstate, exprLocation((Node *)windef->rowPatternMeasures))));
+}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 346fd272b6..20231d9ec0 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -541,6 +541,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_COPY_WHERE:
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_RPR_DEFINE:
/* okay */
break;
@@ -1754,6 +1755,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_RPR_DEFINE:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3133,6 +3135,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "GENERATED AS";
case EXPR_KIND_CYCLE_MARK:
return "CYCLE";
+ case EXPR_KIND_RPR_DEFINE:
+ return "DEFINE";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index b3f0b6a137..2ff3699538 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2656,6 +2656,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_RPR_DEFINE:
+ errkind = true;
+ break;
/*
* There is intentionally no default: case here, so that the
--
2.25.1
----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="v2-0003-Row-pattern-recognition-patch-planner.patch"
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v2] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 ++++
src/backend/catalog/objectaddress.c | 18 ++-
src/backend/catalog/pg_largeobject.c | 18 ++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 104 +++++++++++++++++-
src/test/regress/sql/privileges.sql | 47 ++++++++
14 files changed, 235 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2907079e2a..b8cc822aeb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3330,7 +3330,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 1de4c5c1b4..3b358b7a88 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -50,6 +50,11 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
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 ] }
@@ -81,6 +86,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>
@@ -159,7 +171,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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7abf3c2a74..41baf81a1d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1499,6 +1513,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",
@@ -4324,6 +4341,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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7b536ac6fd..5b330967a6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2000,16 +2000,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)));
}
/*
@@ -3798,6 +3802,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)
@@ -5651,6 +5661,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_LARGEOBJECT:
+ appendStringInfoString(&buffer,
+ " on large objects");
+ break;
}
if (objname)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..578589b457 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -22,6 +22,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"
@@ -41,6 +42,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);
@@ -57,11 +60,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);
@@ -72,6 +82,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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e8b619926e..35535ab390 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -760,7 +760,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
@@ -8170,6 +8170,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17788,6 +17789,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18411,6 +18413,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1..0fad29cbaf 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b8acdd7355..298cbb3d56 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15224,6 +15224,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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4a9ee4a54d..12d002b709 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1184,7 +1184,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"),
@@ -1198,6 +1200,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");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6fee3160f0..03889fc52f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4035,7 +4035,7 @@ psql_completion(const char *text, int start, int end)
* 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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f9a4afd472..6299c29389 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index eb4b762ea1..29f0775373 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2357,11 +2357,110 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+ERROR: permission denied for large object 1007
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+ROLLBACK;
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+ lowrite
+---------
+ 4
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+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;
@@ -2372,7 +2471,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;
@@ -2708,7 +2807,8 @@ SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3
1
1
1
-(5 rows)
+ 1
+(6 rows)
DROP GROUP regress_priv_group1;
DROP GROUP regress_priv_group2;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index eeb4c00292..e8db20573c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1487,11 +1487,58 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ROLLBACK;
+
+BEGIN;
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+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;
--
2.25.1
--Multipart=_Wed__24_Apr_2024_15_32_33_+0900_oAlXWx4kASmsfCdm--
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v3] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Previously, ALTER DEFAULT PRIVILEGE didn't support large objects,
so if we want to allow users other than the owner to use the large
object, we need to grant a privilege on it every time a large object
is created.
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 +++++
src/backend/catalog/objectaddress.c | 18 +++-
src/backend/catalog/pg_largeobject.c | 18 +++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 94 ++++++++++++++++++-
src/test/regress/sql/privileges.sql | 36 +++++++
14 files changed, 215 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2..c8cf56c666 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3322,7 +3322,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fa..3ab695892d 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -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>
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d2abc48fd8..6463ae921a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1511,6 +1525,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",
@@ -4334,6 +4351,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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 85a7b7e641..94f38c2333 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2000,16 +2000,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)));
}
/*
@@ -3798,6 +3802,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)
@@ -5720,6 +5730,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_LARGEOBJECT:
+ appendStringInfoString(&buffer,
+ " on large objects");
+ break;
}
if (objname)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 5d9fdfbd4c..ddadb3224a 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 84cef57a70..a77df8f028 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -756,7 +756,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
@@ -8054,6 +8054,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17672,6 +17673,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18291,6 +18293,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1..0fad29cbaf 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 546e7e4ce1..97484a1b1b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15009,6 +15009,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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c..ee1b96b0b9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1184,7 +1184,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"),
@@ -1198,6 +1200,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");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..7979fd32c6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4032,7 +4032,7 @@ psql_completion(const char *text, int start, int end)
* 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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f8659078ce..cbc7f2e870 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 1d903babd3..270d82112b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2563,11 +2563,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;
@@ -2578,7 +2670,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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 3f54b0f8f0..5598410e44 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1535,11 +1535,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;
--
2.34.1
--Multipart=_Fri__13_Sep_2024_16_18_01_+0900_KlUGdC57lGXrO9ov--
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 ++++
src/backend/catalog/pg_largeobject.c | 18 ++-
src/backend/parser/gram.y | 5 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 104 +++++++++++++++++-
src/test/regress/sql/privileges.sql | 47 ++++++++
9 files changed, 208 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2907079e2a..b8cc822aeb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3330,7 +3330,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 1de4c5c1b4..3b358b7a88 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -50,6 +50,11 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
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 ] }
@@ -81,6 +86,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>
@@ -159,7 +171,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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7abf3c2a74..41baf81a1d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1499,6 +1513,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",
@@ -4324,6 +4341,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;
}
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..578589b457 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -22,6 +22,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"
@@ -41,6 +42,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);
@@ -57,11 +60,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);
@@ -72,6 +82,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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e8b619926e..35535ab390 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -760,7 +760,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
@@ -8170,6 +8170,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17788,6 +17789,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18411,6 +18413,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f9a4afd472..6299c29389 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index eb4b762ea1..29f0775373 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2357,11 +2357,110 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+ERROR: permission denied for large object 1007
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+ROLLBACK;
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+ lowrite
+---------
+ 4
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+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;
@@ -2372,7 +2471,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;
@@ -2708,7 +2807,8 @@ SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3
1
1
1
-(5 rows)
+ 1
+(6 rows)
DROP GROUP regress_priv_group1;
DROP GROUP regress_priv_group2;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index eeb4c00292..e8db20573c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1487,11 +1487,58 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ROLLBACK;
+
+BEGIN;
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+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;
--
2.25.1
--Multipart=_Wed__24_Apr_2024_11_52_42_+0900_jDJ5qZzcvgxN5www--
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 ++++
src/backend/catalog/pg_largeobject.c | 18 ++-
src/backend/parser/gram.y | 5 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 104 +++++++++++++++++-
src/test/regress/sql/privileges.sql | 47 ++++++++
9 files changed, 208 insertions(+), 7 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2907079e2a..b8cc822aeb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3330,7 +3330,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 1de4c5c1b4..3b358b7a88 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -50,6 +50,11 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
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 ] }
@@ -81,6 +86,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>
@@ -159,7 +171,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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7abf3c2a74..41baf81a1d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1499,6 +1513,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",
@@ -4324,6 +4341,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;
}
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..578589b457 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -22,6 +22,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"
@@ -41,6 +42,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);
@@ -57,11 +60,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);
@@ -72,6 +82,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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e8b619926e..35535ab390 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -760,7 +760,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
@@ -8170,6 +8170,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17788,6 +17789,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18411,6 +18413,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f9a4afd472..6299c29389 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index eb4b762ea1..29f0775373 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2357,11 +2357,110 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+ERROR: permission denied for large object 1007
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+ROLLBACK;
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+ lowrite
+---------
+ 4
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+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;
@@ -2372,7 +2471,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;
@@ -2708,7 +2807,8 @@ SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3
1
1
1
-(5 rows)
+ 1
+(6 rows)
DROP GROUP regress_priv_group1;
DROP GROUP regress_priv_group2;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index eeb4c00292..e8db20573c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1487,11 +1487,58 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ROLLBACK;
+
+BEGIN;
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+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;
--
2.25.1
--Multipart=_Wed__24_Apr_2024_11_52_42_+0900_jDJ5qZzcvgxN5www--
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v3] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Previously, ALTER DEFAULT PRIVILEGE didn't support large objects,
so if we want to allow users other than the owner to use the large
object, we need to grant a privilege on it every time a large object
is created.
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 +++++
src/backend/catalog/objectaddress.c | 18 +++-
src/backend/catalog/pg_largeobject.c | 18 +++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 94 ++++++++++++++++++-
src/test/regress/sql/privileges.sql | 36 +++++++
14 files changed, 215 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2..c8cf56c666 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3322,7 +3322,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fa..3ab695892d 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -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>
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d2abc48fd8..6463ae921a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1511,6 +1525,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",
@@ -4334,6 +4351,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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 85a7b7e641..94f38c2333 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2000,16 +2000,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)));
}
/*
@@ -3798,6 +3802,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)
@@ -5720,6 +5730,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_LARGEOBJECT:
+ appendStringInfoString(&buffer,
+ " on large objects");
+ break;
}
if (objname)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 5d9fdfbd4c..ddadb3224a 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 84cef57a70..a77df8f028 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -756,7 +756,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
@@ -8054,6 +8054,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17672,6 +17673,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18291,6 +18293,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1..0fad29cbaf 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 546e7e4ce1..97484a1b1b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15009,6 +15009,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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c..ee1b96b0b9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1184,7 +1184,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"),
@@ -1198,6 +1200,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");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..7979fd32c6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4032,7 +4032,7 @@ psql_completion(const char *text, int start, int end)
* 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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f8659078ce..cbc7f2e870 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 1d903babd3..270d82112b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2563,11 +2563,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;
@@ -2578,7 +2670,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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 3f54b0f8f0..5598410e44 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1535,11 +1535,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;
--
2.34.1
--Multipart=_Fri__13_Sep_2024_16_18_01_+0900_KlUGdC57lGXrO9ov--
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v2] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 ++++
src/backend/catalog/objectaddress.c | 18 ++-
src/backend/catalog/pg_largeobject.c | 18 ++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 104 +++++++++++++++++-
src/test/regress/sql/privileges.sql | 47 ++++++++
14 files changed, 235 insertions(+), 12 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2907079e2a..b8cc822aeb 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3330,7 +3330,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 1de4c5c1b4..3b358b7a88 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -50,6 +50,11 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] }
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 ] }
@@ -81,6 +86,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>
@@ -159,7 +171,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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 7abf3c2a74..41baf81a1d 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1499,6 +1513,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",
@@ -4324,6 +4341,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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7b536ac6fd..5b330967a6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2000,16 +2000,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)));
}
/*
@@ -3798,6 +3802,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)
@@ -5651,6 +5661,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_LARGEOBJECT:
+ appendStringInfoString(&buffer,
+ " on large objects");
+ break;
}
if (objname)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index e235f7c5e6..578589b457 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -22,6 +22,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"
@@ -41,6 +42,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);
@@ -57,11 +60,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);
@@ -72,6 +82,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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e8b619926e..35535ab390 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -760,7 +760,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
@@ -8170,6 +8170,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17788,6 +17789,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18411,6 +18413,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1..0fad29cbaf 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b8acdd7355..298cbb3d56 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15224,6 +15224,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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4a9ee4a54d..12d002b709 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1184,7 +1184,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"),
@@ -1198,6 +1200,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");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6fee3160f0..03889fc52f 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4035,7 +4035,7 @@ psql_completion(const char *text, int start, int end)
* 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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f9a4afd472..6299c29389 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index eb4b762ea1..29f0775373 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2357,11 +2357,110 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+ERROR: permission denied for large object 1007
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+ROLLBACK;
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+ lowrite
+---------
+ 4
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+ lo_unlink
+-----------
+ 1
+(1 row)
+
+SELECT lo_create(1007);
+ lo_create
+-----------
+ 1007
+(1 row)
+
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+ loread
+--------
+ \x
+(1 row)
+
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ERROR: permission denied for large object 1007
+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;
@@ -2372,7 +2471,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;
@@ -2708,7 +2807,8 @@ SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3
1
1
1
-(5 rows)
+ 1
+(6 rows)
DROP GROUP regress_priv_group1;
DROP GROUP regress_priv_group2;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index eeb4c00292..e8db20573c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1487,11 +1487,58 @@ 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.
+--
+\c -
+SET SESSION AUTHORIZATION regress_priv_user1;
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- to be denied
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO public;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+ROLLBACK;
+
+BEGIN;
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES GRANT SELECT, UPDATE ON LARGE OBJECTS TO regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- ok
+
+SET SESSION AUTHORIZATION regress_priv_user1;
+ALTER DEFAULT PRIVILEGES REVOKE UPDATE ON LARGE OBJECTS FROM regress_priv_user2;
+SELECT lo_unlink(1007);
+SELECT lo_create(1007);
+SET SESSION AUTHORIZATION regress_priv_user2;
+SELECT loread(lo_open(1007, x'40000'::int), 32); -- ok
+SELECT lowrite(lo_open(1007, x'20000'::int), 'abcd'); -- to be denied
+
+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;
--
2.25.1
--Multipart=_Wed__24_Apr_2024_15_32_33_+0900_oAlXWx4kASmsfCdm--
^ permalink raw reply [nested|flat] 33+ messages in thread
* [PATCH v5] Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-03-08 08:43 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2024-03-08 08:43 UTC (permalink / raw)
Previously, ALTER DEFAULT PRIVILEGE didn't support large objects,
so if we want to allow users other than the owner to use the large
object, we need to grant a privilege on it every time a large object
is created.
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 19 +++-
src/backend/catalog/aclchk.c | 21 +++++
src/backend/catalog/objectaddress.c | 18 +++-
src/backend/catalog/pg_largeobject.c | 18 +++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.in.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 94 ++++++++++++++++++-
src/test/regress/sql/privileges.sql | 36 +++++++
14 files changed, 217 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4558f940aaf..45ba9c5118f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3360,7 +3360,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fab..6acd0f1df91 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 02a754cc30a..9ca8a88dc91 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d8eb8d3deaa..b63fd57dc04 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -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)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 0a477a8e8a9..71a9cc134e1 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6a094ecc54f..f1156e2fca3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5ae77f76367..ab0e9e6da3c 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 04c87ba8854..817cedef32c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e038e9dc9e2..8970677ac64 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -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");
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 98951aef82c..c916b9299a8 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 728024b1fa7..ce6e5098eaf 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..a4af3f717a1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 5588d83e1bf..1fddb13b6ae 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 286b1d03756..85d7280f35f 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -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;
--
2.34.1
--Multipart=_Thu__3_Apr_2025_23_04_18_+0900_OP_tfBIdDuoj6p3Q--
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
@ 2024-09-13 07:18 Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Yugo Nagata @ 2024-09-13 07:18 UTC (permalink / raw)
To: Yugo NAGATA <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Fri, 26 Apr 2024 17:54:06 +0900
Yugo NAGATA <[email protected]> wrote:
> On Wed, 24 Apr 2024 16:08:39 -0500
> Nathan Bossart <[email protected]> wrote:
>
> > On Tue, Apr 23, 2024 at 11:47:38PM -0400, Tom Lane wrote:
> > > On the whole I find this proposed feature pretty unexciting
> > > and dubiously worthy of the implementation/maintenance effort.
> >
> > I don't have any particularly strong feelings on $SUBJECT, but I'll admit
> > I'd be much more interested in resolving any remaining reasons folks are
> > using large objects over TOAST. I see a couple of reasons listed in the
> > docs [0] that might be worth examining.
> >
> > [0] https://www.postgresql.org/docs/devel/lo-intro.html
>
> If we could replace large objects with BYTEA in any use cases, large objects
> would be completely obsolete. However, currently some users use large objects
> in fact, so improvement in this feature seems beneficial for them.
I've attached a updated patch. The test is rewritten using has_largeobject_privilege()
function instead of calling loread & lowrite, which makes the test a bit simpler.
Thare are no other changes.
Regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
Attachments:
[text/x-diff] v3-0001-Extend-ALTER-DEFAULT-PRIVILEGES-for-large-objects.patch (19.2K, ../../[email protected]/2-v3-0001-Extend-ALTER-DEFAULT-PRIVILEGES-for-large-objects.patch)
download | inline diff:
From a27680b9f3031b0995fe20dd2d166e07a17ffeca Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email protected]>
Date: Fri, 8 Mar 2024 17:43:43 +0900
Subject: [PATCH v3] Extend ALTER DEFAULT PRIVILEGES for large objects
Previously, ALTER DEFAULT PRIVILEGE didn't support large objects,
so if we want to allow users other than the owner to use the large
object, we need to grant a privilege on it every time a large object
is created.
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 +++++
src/backend/catalog/objectaddress.c | 18 +++-
src/backend/catalog/pg_largeobject.c | 18 +++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 94 ++++++++++++++++++-
src/test/regress/sql/privileges.sql | 36 +++++++
14 files changed, 215 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2..c8cf56c666 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3322,7 +3322,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fa..3ab695892d 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -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>
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index d2abc48fd8..6463ae921a 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1077,6 +1077,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);
@@ -1268,6 +1272,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);
@@ -1511,6 +1525,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",
@@ -4334,6 +4351,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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 85a7b7e641..94f38c2333 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2000,16 +2000,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)));
}
/*
@@ -3798,6 +3802,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)
@@ -5720,6 +5730,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_LARGEOBJECT:
+ appendStringInfoString(&buffer,
+ " on large objects");
+ break;
}
if (objname)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 5d9fdfbd4c..ddadb3224a 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 84cef57a70..a77df8f028 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -756,7 +756,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
@@ -8054,6 +8054,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17672,6 +17673,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18291,6 +18293,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5649859aa1..0fad29cbaf 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 546e7e4ce1..97484a1b1b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15009,6 +15009,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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c..ee1b96b0b9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1184,7 +1184,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"),
@@ -1198,6 +1200,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");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a7ccde6d7d..7979fd32c6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4032,7 +4032,7 @@ psql_completion(const char *text, int start, int end)
* 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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d272cdf08b..f9f002fa45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f8659078ce..cbc7f2e870 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 1d903babd3..270d82112b 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2563,11 +2563,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;
@@ -2578,7 +2670,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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 3f54b0f8f0..5598410e44 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1535,11 +1535,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;
--
2.34.1
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
@ 2025-01-22 12:30 ` Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Laurenz Albe @ 2025-01-22 12:30 UTC (permalink / raw)
To: Yugo Nagata <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Fri, 2024-09-13 at 16:18 +0900, Yugo Nagata wrote:
> I've attached a updated patch. The test is rewritten using has_largeobject_privilege()
> function instead of calling loread & lowrite, which makes the test a bit simpler.
> Thare are no other changes.
When I tried to apply this patch, I found that it doesn't apply any
more since commit f391d9dc93 renamed tab-complete.c to tab-complete.in.c.
Attached is a rebased patch.
I agree that large objects are a feature that should fade out (alas,
the JDBC driver still uses it for BLOBs). But this patch is not big
or complicated and is unlikely to create a big maintenance burden.
So I am somewhat for committing it. It works as advertised.
If you are fine with my rebased patch, I can mark it as "ready for
committer". If it actually gets committed depends on whether there
is a committer who thinks it worth the effort or not.
Yours,
Laurenz Albe
Attachments:
[text/x-patch] v4-0001-Extend-ALTER-DEFAULT-PRIVILEGES-for-large-objects.patch (19.3K, ../../[email protected]/2-v4-0001-Extend-ALTER-DEFAULT-PRIVILEGES-for-large-objects.patch)
download | inline diff:
From 55c0ed8c09b8bd83ced894a349c01f84b7c47e82 Mon Sep 17 00:00:00 2001
From: Laurenz Albe <[email protected]>
Date: Wed, 22 Jan 2025 13:15:27 +0100
Subject: [PATCH v4] Extend ALTER DEFAULT PRIVILEGES for large objects
Previously, ALTER DEFAULT PRIVILEGE didn't support large objects,
so if we want to allow users other than the owner to use the large
object, we need to grant a privilege on it every time a large object
is created.
Author: Haruka Takatsuka
Author: Yugo Nagata
Discussion: https://postgr.es/m/20240424115242.236b499b2bed5b7a27f7a418%40sraoss.co.jp
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 15 ++-
src/backend/catalog/aclchk.c | 21 +++++
src/backend/catalog/objectaddress.c | 18 +++-
src/backend/catalog/pg_largeobject.c | 18 +++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.in.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 94 ++++++++++++++++++-
src/test/regress/sql/privileges.sql | 36 +++++++
14 files changed, 215 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index d3036c5ba9d..47c185916aa 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3338,7 +3338,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fab..3ab695892da 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -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>
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 02a754cc30a..9ca8a88dc91 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d8eb8d3deaa..b63fd57dc04 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -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)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 0a477a8e8a9..71a9cc134e1 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6079de70e09..9bab74ebb39 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -746,7 +746,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
@@ -8141,6 +8141,7 @@ defacl_privilege_target:
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
+ | LARGE_P OBJECTS_P { $$ = OBJECT_LARGEOBJECT; }
;
@@ -17809,6 +17810,7 @@ unreserved_keyword:
| NOWAIT
| NULLS_P
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
@@ -18430,6 +18432,7 @@ bare_label_keyword:
| NULLS_P
| NUMERIC
| OBJECT_P
+ | OBJECTS_P
| OF
| OFF
| OIDS
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5ae77f76367..ab0e9e6da3c 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8f73a5df956..67c2540cbc6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15195,6 +15195,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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2ef99971ac0..4538ea89e17 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1221,7 +1221,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"),
@@ -1235,6 +1237,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");
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 81cbf10aa28..600097da259 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4424,7 +4424,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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 728024b1fa7..ce6e5098eaf 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index cf2917ad07e..58e769bce1b 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 6b01313101b..cf575b46bc8 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2637,11 +2637,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;
@@ -2652,7 +2744,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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 60e7443bf59..57c043a0f7d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1571,11 +1571,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;
--
2.48.1
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
@ 2025-01-23 10:22 ` Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Yugo NAGATA @ 2025-01-23 10:22 UTC (permalink / raw)
To: Laurenz Albe <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Wed, 22 Jan 2025 13:30:17 +0100
Laurenz Albe <[email protected]> wrote:
> On Fri, 2024-09-13 at 16:18 +0900, Yugo Nagata wrote:
> > I've attached a updated patch. The test is rewritten using has_largeobject_privilege()
> > function instead of calling loread & lowrite, which makes the test a bit simpler.
> > Thare are no other changes.
>
> When I tried to apply this patch, I found that it doesn't apply any
> more since commit f391d9dc93 renamed tab-complete.c to tab-complete.in.c.
>
> Attached is a rebased patch.
Thank you for updating the patch!
> I agree that large objects are a feature that should fade out (alas,
> the JDBC driver still uses it for BLOBs). But this patch is not big
> or complicated and is unlikely to create a big maintenance burden.
>
> So I am somewhat for committing it. It works as advertised.
> If you are fine with my rebased patch, I can mark it as "ready for
> committer". If it actually gets committed depends on whether there
> is a committer who thinks it worth the effort or not.
I confirmed the patch and I am fine with it.
Regards,
Yugo Nagata
--
Yugo NAGATA <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
@ 2025-04-01 17:35 ` Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Fujii Masao @ 2025-04-01 17:35 UTC (permalink / raw)
To: Yugo NAGATA <[email protected]>; Laurenz Albe <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/01/23 19:22, Yugo NAGATA wrote:
> On Wed, 22 Jan 2025 13:30:17 +0100
> Laurenz Albe <[email protected]> wrote:
>
>> On Fri, 2024-09-13 at 16:18 +0900, Yugo Nagata wrote:
>>> I've attached a updated patch. The test is rewritten using has_largeobject_privilege()
>>> function instead of calling loread & lowrite, which makes the test a bit simpler.
>>> Thare are no other changes.
>>
>> When I tried to apply this patch, I found that it doesn't apply any
>> more since commit f391d9dc93 renamed tab-complete.c to tab-complete.in.c.
>>
>> Attached is a rebased patch.
>
> Thank you for updating the patch!
>
>> I agree that large objects are a feature that should fade out (alas,
>> the JDBC driver still uses it for BLOBs). But this patch is not big
>> or complicated and is unlikely to create a big maintenance burden.
>>
>> So I am somewhat for committing it. It works as advertised.
>> If you are fine with my rebased patch, I can mark it as "ready for
>> committer". If it actually gets committed depends on whether there
>> is a committer who thinks it worth the effort or not.
>
> I confirmed the patch and I am fine with it.
I've started reviewing this patch since it's marked as "ready for committer".
I know of several systems that use large objects, and I believe
this feature would be beneficial for them. Overall, I like the idea.
The latest patch looks good to me. I just have one minor comment:
> only the privileges for schemas, tables (including views and foreign
> tables), sequences, functions, and types (including domains) can be
> altered.
In alter_default_privileges.sgml, this part should also mention large objects?
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-04-03 14:04 ` Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Yugo NAGATA @ 2025-04-03 14:04 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Wed, 2 Apr 2025 02:35:35 +0900
Fujii Masao <[email protected]> wrote:
>
>
> On 2025/01/23 19:22, Yugo NAGATA wrote:
> > On Wed, 22 Jan 2025 13:30:17 +0100
> > Laurenz Albe <[email protected]> wrote:
> >
> >> On Fri, 2024-09-13 at 16:18 +0900, Yugo Nagata wrote:
> >>> I've attached a updated patch. The test is rewritten using has_largeobject_privilege()
> >>> function instead of calling loread & lowrite, which makes the test a bit simpler.
> >>> Thare are no other changes.
> >>
> >> When I tried to apply this patch, I found that it doesn't apply any
> >> more since commit f391d9dc93 renamed tab-complete.c to tab-complete.in.c.
> >>
> >> Attached is a rebased patch.
> >
> > Thank you for updating the patch!
> >
> >> I agree that large objects are a feature that should fade out (alas,
> >> the JDBC driver still uses it for BLOBs). But this patch is not big
> >> or complicated and is unlikely to create a big maintenance burden.
> >>
> >> So I am somewhat for committing it. It works as advertised.
> >> If you are fine with my rebased patch, I can mark it as "ready for
> >> committer". If it actually gets committed depends on whether there
> >> is a committer who thinks it worth the effort or not.
> >
> > I confirmed the patch and I am fine with it.
>
> I've started reviewing this patch since it's marked as "ready for committer".
Thank you for your reviewing this patch!
> I know of several systems that use large objects, and I believe
> this feature would be beneficial for them. Overall, I like the idea.
>
>
> The latest patch looks good to me. I just have one minor comment:
>
> > only the privileges for schemas, tables (including views and foreign
> > tables), sequences, functions, and types (including domains) can be
> > altered.
>
> In alter_default_privileges.sgml, this part should also mention large objects?
Agreed. I attached a updated patch.
Regards,
Yugo Nagata
--
Yugo NAGATA <[email protected]>
Attachments:
[text/x-diff] v5-0001-Extend-ALTER-DEFAULT-PRIVILEGES-for-large-objects.patch (19.9K, ../../[email protected]/2-v5-0001-Extend-ALTER-DEFAULT-PRIVILEGES-for-large-objects.patch)
download | inline diff:
From 98393b8609a97a33f1cf5ed69cb8cc28d07b25df Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email protected]>
Date: Fri, 8 Mar 2024 17:43:43 +0900
Subject: [PATCH v5] Extend ALTER DEFAULT PRIVILEGES for large objects
Previously, ALTER DEFAULT PRIVILEGE didn't support large objects,
so if we want to allow users other than the owner to use the large
object, we need to grant a privilege on it every time a large object
is created.
Original patch by Haruka Takatsuka, some fixes and tests by
Yugo Nagata.
---
doc/src/sgml/catalogs.sgml | 3 +-
.../sgml/ref/alter_default_privileges.sgml | 19 +++-
src/backend/catalog/aclchk.c | 21 +++++
src/backend/catalog/objectaddress.c | 18 +++-
src/backend/catalog/pg_largeobject.c | 18 +++-
src/backend/parser/gram.y | 5 +-
src/bin/pg_dump/dumputils.c | 3 +-
src/bin/pg_dump/pg_dump.c | 3 +
src/bin/psql/describe.c | 6 +-
src/bin/psql/tab-complete.in.c | 2 +-
src/include/catalog/pg_default_acl.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/privileges.out | 94 ++++++++++++++++++-
src/test/regress/sql/privileges.sql | 36 +++++++
14 files changed, 217 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4558f940aaf..45ba9c5118f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3360,7 +3360,8 @@ SCRAM-SHA-256$<replaceable><iteration count></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>
diff --git a/doc/src/sgml/ref/alter_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml
index 89aacec4fab..6acd0f1df91 100644
--- a/doc/src/sgml/ref/alter_default_privileges.sgml
+++ b/doc/src/sgml/ref/alter_default_privileges.sgml
@@ -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>
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 02a754cc30a..9ca8a88dc91 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -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;
}
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d8eb8d3deaa..b63fd57dc04 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -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)
diff --git a/src/backend/catalog/pg_largeobject.c b/src/backend/catalog/pg_largeobject.c
index 0a477a8e8a9..71a9cc134e1 100644
--- a/src/backend/catalog/pg_largeobject.c
+++ b/src/backend/catalog/pg_largeobject.c
@@ -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;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6a094ecc54f..f1156e2fca3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 5ae77f76367..ab0e9e6da3c 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -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");
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 04c87ba8854..817cedef32c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -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",
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e038e9dc9e2..8970677ac64 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -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");
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 98951aef82c..c916b9299a8 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -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",
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 728024b1fa7..ce6e5098eaf 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -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 */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 40cf090ce61..a4af3f717a1 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -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)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 5588d83e1bf..1fddb13b6ae 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -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;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 286b1d03756..85d7280f35f 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -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;
--
2.34.1
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
@ 2025-04-03 15:21 ` Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Fujii Masao @ 2025-04-03 15:21 UTC (permalink / raw)
To: Yugo NAGATA <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/04/03 23:04, Yugo NAGATA wrote:
> On Wed, 2 Apr 2025 02:35:35 +0900
> Fujii Masao <[email protected]> wrote:
>
>>
>>
>> On 2025/01/23 19:22, Yugo NAGATA wrote:
>>> On Wed, 22 Jan 2025 13:30:17 +0100
>>> Laurenz Albe <[email protected]> wrote:
>>>
>>>> On Fri, 2024-09-13 at 16:18 +0900, Yugo Nagata wrote:
>>>>> I've attached a updated patch. The test is rewritten using has_largeobject_privilege()
>>>>> function instead of calling loread & lowrite, which makes the test a bit simpler.
>>>>> Thare are no other changes.
>>>>
>>>> When I tried to apply this patch, I found that it doesn't apply any
>>>> more since commit f391d9dc93 renamed tab-complete.c to tab-complete.in.c.
>>>>
>>>> Attached is a rebased patch.
>>>
>>> Thank you for updating the patch!
>>>
>>>> I agree that large objects are a feature that should fade out (alas,
>>>> the JDBC driver still uses it for BLOBs). But this patch is not big
>>>> or complicated and is unlikely to create a big maintenance burden.
>>>>
>>>> So I am somewhat for committing it. It works as advertised.
>>>> If you are fine with my rebased patch, I can mark it as "ready for
>>>> committer". If it actually gets committed depends on whether there
>>>> is a committer who thinks it worth the effort or not.
>>>
>>> I confirmed the patch and I am fine with it.
>>
>> I've started reviewing this patch since it's marked as "ready for committer".
>
> Thank you for your reviewing this patch!
>
>> I know of several systems that use large objects, and I believe
>> this feature would be beneficial for them. Overall, I like the idea.
>>
>>
>> The latest patch looks good to me. I just have one minor comment:
>>
>>> only the privileges for schemas, tables (including views and foreign
>>> tables), sequences, functions, and types (including domains) can be
>>> altered.
>>
>> In alter_default_privileges.sgml, this part should also mention large objects?
>
> Agreed. I attached a updated patch.
Thanks for updating the patch!
If there are no objections, I'll proceed with committing it using the following commit log.
----------------
Extend ALTER DEFAULT PRIVILEGES to define default privileges for large objects.
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/[email protected]
----------------
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-04-04 10:18 ` Fujii Masao <[email protected]>
2025-04-04 14:47 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Nathan Bossart <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
0 siblings, 2 replies; 33+ messages in thread
From: Fujii Masao @ 2025-04-04 10:18 UTC (permalink / raw)
To: Yugo NAGATA <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/04/04 0:21, Fujii Masao wrote:
> Thanks for updating the patch!
>
> If there are no objections, I'll proceed with committing it using the following commit log.
I've pushed the patch. Thanks!
While testing the feature, I noticed that psql doesn't complete
"ALTER DEFAULT PRIVILEGES GRANT/REVOKE ... ON LARGE OBJECTS" or
"GRANT/REVOKE ... ON LARGE OBJECT ..." with TO/FROM. The attached
patch adds tab-completion support for both cases.
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
From 02c1811af49b5f417af71b601cb60621640a14b4 Mon Sep 17 00:00:00 2001
From: Fujii Masao <[email protected]>
Date: Fri, 4 Apr 2025 10:51:20 +0900
Subject: [PATCH v1] psql: Improve psql tab completion for GRANT/REVOKE on
large objects.
This commit enhances psql's tab completion to support TO/FROM
after "GRANT/REVOKE ... ON LARGE OBJECT ...". Additionally,
since "ALTER DEFAULT PRIVILEGES" now supports large objects,
tab completion is also updated for "GRANT/REVOKE ... ON LARGE OBJECTS"
with TO/FROM.
---
src/bin/psql/tab-complete.in.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index c916b9299a8..bdeb95fb3c8 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4602,6 +4602,26 @@ match_previous_words(int pattern_id,
COMPLETE_WITH("FROM");
}
+ /* Complete "GRANT/REVOKE * ON LARGE OBJECT *" with TO/FROM */
+ else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "LARGE", "OBJECT", MatchAny) ||
+ TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "LARGE", "OBJECT", MatchAny))
+ {
+ if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+
+ /* Complete "GRANT/REVOKE * ON LARGE OBJECTS" with TO/FROM */
+ else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "LARGE", "OBJECTS") ||
+ TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "LARGE", "OBJECTS"))
+ {
+ if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+
/* GROUP BY */
else if (TailMatches("FROM", MatchAny, "GROUP"))
COMPLETE_WITH("BY");
--
2.48.1
Attachments:
[text/plain] v1-0001-psql-Improve-psql-tab-completion-for-GRANT-REVOKE.patch (1.7K, ../../[email protected]/2-v1-0001-psql-Improve-psql-tab-completion-for-GRANT-REVOKE.patch)
download | inline diff:
From 02c1811af49b5f417af71b601cb60621640a14b4 Mon Sep 17 00:00:00 2001
From: Fujii Masao <[email protected]>
Date: Fri, 4 Apr 2025 10:51:20 +0900
Subject: [PATCH v1] psql: Improve psql tab completion for GRANT/REVOKE on
large objects.
This commit enhances psql's tab completion to support TO/FROM
after "GRANT/REVOKE ... ON LARGE OBJECT ...". Additionally,
since "ALTER DEFAULT PRIVILEGES" now supports large objects,
tab completion is also updated for "GRANT/REVOKE ... ON LARGE OBJECTS"
with TO/FROM.
---
src/bin/psql/tab-complete.in.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index c916b9299a8..bdeb95fb3c8 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4602,6 +4602,26 @@ match_previous_words(int pattern_id,
COMPLETE_WITH("FROM");
}
+ /* Complete "GRANT/REVOKE * ON LARGE OBJECT *" with TO/FROM */
+ else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "LARGE", "OBJECT", MatchAny) ||
+ TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "LARGE", "OBJECT", MatchAny))
+ {
+ if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+
+ /* Complete "GRANT/REVOKE * ON LARGE OBJECTS" with TO/FROM */
+ else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "LARGE", "OBJECTS") ||
+ TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "LARGE", "OBJECTS"))
+ {
+ if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+
/* GROUP BY */
else if (TailMatches("FROM", MatchAny, "GROUP"))
COMPLETE_WITH("BY");
--
2.48.1
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-04-04 14:47 ` Nathan Bossart <[email protected]>
2025-04-04 15:10 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
1 sibling, 1 reply; 33+ messages in thread
From: Nathan Bossart @ 2025-04-04 14:47 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Yugo NAGATA <[email protected]>; Laurenz Albe <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Fri, Apr 04, 2025 at 07:18:11PM +0900, Fujii Masao wrote:
> I've pushed the patch. Thanks!
Just a heads up, I fixed a pgindent issue in this commit (see commits
e1a8b1ad58 and 742317a80f). I'd ordinarily just report it, but since we're
nearing feature freeze, I just fixed it because my workflow (and presumably
others') involves running pgindent on the entire tree periodically.
--
nathan
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 14:47 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Nathan Bossart <[email protected]>
@ 2025-04-04 15:10 ` Fujii Masao <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Fujii Masao @ 2025-04-04 15:10 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Yugo NAGATA <[email protected]>; Laurenz Albe <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/04/04 23:47, Nathan Bossart wrote:
> On Fri, Apr 04, 2025 at 07:18:11PM +0900, Fujii Masao wrote:
>> I've pushed the patch. Thanks!
>
> Just a heads up, I fixed a pgindent issue in this commit (see commits
> e1a8b1ad58 and 742317a80f). I'd ordinarily just report it, but since we're
> nearing feature freeze, I just fixed it because my workflow (and presumably
> others') involves running pgindent on the entire tree periodically.
Thanks a lot!
Regards,
--
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-04-08 03:28 ` Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
1 sibling, 1 reply; 33+ messages in thread
From: Yugo NAGATA @ 2025-04-08 03:28 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Fri, 4 Apr 2025 19:18:11 +0900
Fujii Masao <[email protected]> wrote:
>
>
> On 2025/04/04 0:21, Fujii Masao wrote:
> > Thanks for updating the patch!
> >
> > If there are no objections, I'll proceed with committing it using the following commit log.
>
> I've pushed the patch. Thanks!
Thank you!
> While testing the feature, I noticed that psql doesn't complete
> "ALTER DEFAULT PRIVILEGES GRANT/REVOKE ... ON LARGE OBJECTS" or
> "GRANT/REVOKE ... ON LARGE OBJECT ..." with TO/FROM. The attached
> patch adds tab-completion support for both cases.
This patch looks good to me. This works as expected.
While looking into this patch, I found that the tab completion suggests
TO/FROM even after "LARGE OBJECT", but it is not correct because
there should be largeobject id at that place. This is same for the
"FOREIGN SERVER", server names should be suggested ratar than TO/FROM
in this case.
The additional patch 0002 fixed to prevents to suggest TO or FROM right
after LARGE OBJECT or FOREIGN SERVER. Also, it allows to suggest list of
foreign server names after FOREIGN SERVER.
The 0001 patch is the same you proposed.
Regards,
Yugo Nagata
--
Yugo NAGATA <[email protected]>
Attachments:
[text/x-diff] 0002-psql-Some-improvement-of-tab-completion-for-GRANT-RE.patch (1.7K, ../../[email protected]/2-0002-psql-Some-improvement-of-tab-completion-for-GRANT-RE.patch)
download | inline diff:
From 64d5aead5ab080d40fa85f9bd51cb0a09490266f Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email protected]>
Date: Tue, 8 Apr 2025 12:15:45 +0900
Subject: [PATCH 2/2] psql: Some improvement of tab completion for GRANT/REVOKE
This prevents to suggest TO or FROM just after LARGE OBJECT
or FOREIGN SERVER because there should be largeobject id or
server name at that place. Also, it allows to suggest list of
foreign server names after FOREIGN SERVER.
---
src/bin/psql/tab-complete.in.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index bdeb95fb3c8..c58ed27e872 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4567,10 +4567,18 @@ match_previous_words(int pattern_id,
else if (Matches("ALTER", "DEFAULT", "PRIVILEGES", MatchAnyN, "TO", MatchAny))
COMPLETE_WITH("WITH GRANT OPTION");
/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
- else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
- COMPLETE_WITH("TO");
- else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
- COMPLETE_WITH("FROM");
+ else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
+ {
+ if (TailMatches("FOREIGN", "SERVER"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+ else if (!TailMatches("LARGE", "OBJECT"))
+ {
+ if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+ }
/* Complete "GRANT/REVOKE * ON ALL * IN SCHEMA *" with TO/FROM */
else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL", MatchAny, "IN", "SCHEMA", MatchAny) ||
--
2.34.1
[text/x-diff] 0001-psql-Improve-psql-tab-completion-for-GRANT-REVOKE-on.patch (1.7K, ../../[email protected]/3-0001-psql-Improve-psql-tab-completion-for-GRANT-REVOKE-on.patch)
download | inline diff:
From 8402036dcc5e32a249a7af0fb9e27c31ba8b72a6 Mon Sep 17 00:00:00 2001
From: Fujii Masao <[email protected]>
Date: Fri, 4 Apr 2025 10:51:20 +0900
Subject: [PATCH 1/2] psql: Improve psql tab completion for GRANT/REVOKE on
large objects.
This commit enhances psql's tab completion to support TO/FROM
after "GRANT/REVOKE ... ON LARGE OBJECT ...". Additionally,
since "ALTER DEFAULT PRIVILEGES" now supports large objects,
tab completion is also updated for "GRANT/REVOKE ... ON LARGE OBJECTS"
with TO/FROM.
---
src/bin/psql/tab-complete.in.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index c916b9299a8..bdeb95fb3c8 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -4602,6 +4602,26 @@ match_previous_words(int pattern_id,
COMPLETE_WITH("FROM");
}
+ /* Complete "GRANT/REVOKE * ON LARGE OBJECT *" with TO/FROM */
+ else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "LARGE", "OBJECT", MatchAny) ||
+ TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "LARGE", "OBJECT", MatchAny))
+ {
+ if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+
+ /* Complete "GRANT/REVOKE * ON LARGE OBJECTS" with TO/FROM */
+ else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "LARGE", "OBJECTS") ||
+ TailMatches("REVOKE", "GRANT", "OPTION", "FOR", MatchAny, "ON", "LARGE", "OBJECTS"))
+ {
+ if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+
/* GROUP BY */
else if (TailMatches("FROM", MatchAny, "GROUP"))
COMPLETE_WITH("BY");
--
2.34.1
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
@ 2025-06-11 02:49 ` Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Yugo Nagata @ 2025-06-11 02:49 UTC (permalink / raw)
To: Yugo NAGATA <[email protected]>; +Cc: Fujii Masao <[email protected]>; Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Tue, 8 Apr 2025 12:28:57 +0900
Yugo NAGATA <[email protected]> wrote:
> On Fri, 4 Apr 2025 19:18:11 +0900
> Fujii Masao <[email protected]> wrote:
>
> >
> >
> > On 2025/04/04 0:21, Fujii Masao wrote:
> > > Thanks for updating the patch!
> > >
> > > If there are no objections, I'll proceed with committing it using the following commit log.
> >
> > I've pushed the patch. Thanks!
>
> Thank you!
>
>
> > While testing the feature, I noticed that psql doesn't complete
> > "ALTER DEFAULT PRIVILEGES GRANT/REVOKE ... ON LARGE OBJECTS" or
> > "GRANT/REVOKE ... ON LARGE OBJECT ..." with TO/FROM. The attached
> > patch adds tab-completion support for both cases.
>
> This patch looks good to me. This works as expected.
>
> While looking into this patch, I found that the tab completion suggests
> TO/FROM even after "LARGE OBJECT", but it is not correct because
> there should be largeobject id at that place. This is same for the
> "FOREIGN SERVER", server names should be suggested ratar than TO/FROM
> in this case.
>
> The additional patch 0002 fixed to prevents to suggest TO or FROM right
> after LARGE OBJECT or FOREIGN SERVER. Also, it allows to suggest list of
> foreign server names after FOREIGN SERVER.
>
While looking at the thread [1], I've remembered this thread.
The patches in this thread are partially v18-related, but include
enhancement or fixes for existing feature, so should they be postponed
to v19, or should be separated properly to v18 part and other?
[1] https://www.postgresql.org/message-id/70372bdd-4399-4d5b-ab4f-6d4487a4911a%40oss.nttdata.com
Best regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
@ 2025-06-11 04:33 ` Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 05:03 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
0 siblings, 2 replies; 33+ messages in thread
From: Fujii Masao @ 2025-06-11 04:33 UTC (permalink / raw)
To: Yugo Nagata <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/06/11 11:49, Yugo Nagata wrote:
> While looking at the thread [1], I've remembered this thread.
> The patches in this thread are partially v18-related, but include
> enhancement or fixes for existing feature, so should they be postponed
> to v19, or should be separated properly to v18 part and other?
>
> [1] https://www.postgresql.org/message-id/70372bdd-4399-4d5b-ab4f-6d4487a4911a%40oss.nttdata.com
I see these patches more as enhancements to psql tab-completion,
rather than fixes for clear oversights in the original commit.
For example, if tab-completion for ALTER DEFAULT PRIVILEGES had
completely missed LARGE OBJECTS, that would be an obvious oversight.
But these patches go beyond that kind of issue.
That said, if others think it's appropriate to include them in v18
for consistency or completeness, I'm fine with that.
Regarding the 0002 patch:
- else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
- COMPLETE_WITH("TO");
- else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
- COMPLETE_WITH("FROM");
+ else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
+ {
+ if (TailMatches("FOREIGN", "SERVER"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+ else if (!TailMatches("LARGE", "OBJECT"))
+ {
+ if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
+ }
Wouldn't this change break the case where "GRANT ... ON TABLE ... <TAB>"
is supposed to complete with "TO"?
Regards,
--
Fujii Masao
NTT DATA Japan Corporation
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-06-11 04:57 ` Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
1 sibling, 1 reply; 33+ messages in thread
From: Yugo Nagata @ 2025-06-11 04:57 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Wed, 11 Jun 2025 13:33:07 +0900
Fujii Masao <[email protected]> wrote:
>
>
> On 2025/06/11 11:49, Yugo Nagata wrote:
> > While looking at the thread [1], I've remembered this thread.
> > The patches in this thread are partially v18-related, but include
> > enhancement or fixes for existing feature, so should they be postponed
> > to v19, or should be separated properly to v18 part and other?
> >
> > [1] https://www.postgresql.org/message-id/70372bdd-4399-4d5b-ab4f-6d4487a4911a%40oss.nttdata.com
>
> I see these patches more as enhancements to psql tab-completion,
> rather than fixes for clear oversights in the original commit.
>
> For example, if tab-completion for ALTER DEFAULT PRIVILEGES had
> completely missed LARGE OBJECTS, that would be an obvious oversight.
> But these patches go beyond that kind of issue.
>
> That said, if others think it's appropriate to include them in v18
> for consistency or completeness, I'm fine with that.
>
> Regarding the 0002 patch:
>
> - else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
> - COMPLETE_WITH("TO");
> - else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
> - COMPLETE_WITH("FROM");
> + else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
> + {
> + if (TailMatches("FOREIGN", "SERVER"))
> + COMPLETE_WITH_QUERY(Query_for_list_of_servers);
> + else if (!TailMatches("LARGE", "OBJECT"))
> + {
> + if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
> + COMPLETE_WITH("TO");
> + else
> + COMPLETE_WITH("FROM");
> + }
> + }
>
> Wouldn't this change break the case where "GRANT ... ON TABLE ... <TAB>"
> is supposed to complete with "TO"?
Sorry, I made a stupid mistake.
> + else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
This should be "GRANT|REVOKE".
I've attached update patches. (There is no change on 0001.)
Best regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
@ 2025-07-09 11:42 ` Fujii Masao <[email protected]>
2025-07-10 01:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Fujii Masao @ 2025-07-09 11:42 UTC (permalink / raw)
To: Yugo Nagata <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/06/11 13:57, Yugo Nagata wrote:
> On Wed, 11 Jun 2025 13:33:07 +0900
> Fujii Masao <[email protected]> wrote:
>
>>
>>
>> On 2025/06/11 11:49, Yugo Nagata wrote:
>>> While looking at the thread [1], I've remembered this thread.
>>> The patches in this thread are partially v18-related, but include
>>> enhancement or fixes for existing feature, so should they be postponed
>>> to v19, or should be separated properly to v18 part and other?
>>>
>>> [1] https://www.postgresql.org/message-id/70372bdd-4399-4d5b-ab4f-6d4487a4911a%40oss.nttdata.com
>>
>> I see these patches more as enhancements to psql tab-completion,
>> rather than fixes for clear oversights in the original commit.
>>
>> For example, if tab-completion for ALTER DEFAULT PRIVILEGES had
>> completely missed LARGE OBJECTS, that would be an obvious oversight.
>> But these patches go beyond that kind of issue.
>>
>> That said, if others think it's appropriate to include them in v18
>> for consistency or completeness, I'm fine with that.
>>
>> Regarding the 0002 patch:
>>
>> - else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
>> - COMPLETE_WITH("TO");
>> - else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
>> - COMPLETE_WITH("FROM");
>> + else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
>> + {
>> + if (TailMatches("FOREIGN", "SERVER"))
>> + COMPLETE_WITH_QUERY(Query_for_list_of_servers);
>> + else if (!TailMatches("LARGE", "OBJECT"))
>> + {
>> + if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
>> + COMPLETE_WITH("TO");
>> + else
>> + COMPLETE_WITH("FROM");
>> + }
>> + }
>>
>> Wouldn't this change break the case where "GRANT ... ON TABLE ... <TAB>"
>> is supposed to complete with "TO"?
>
> Sorry, I made a stupid mistake.
>
>> + else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
>
> This should be "GRANT|REVOKE".
>
> I've attached update patches. (There is no change on 0001.)
Thanks for updating the patch! At first I've pushed the 0001 patch.
As for the 0002 patch:
+ if (TailMatches("FOREIGN", "SERVER"))
+ COMPLETE_WITH_QUERY(Query_for_list_of_servers);
This part seems not needed, since we already have the following tab-completion code:
/* FOREIGN SERVER */
else if (TailMatches("FOREIGN", "SERVER"))
COMPLETE_WITH_QUERY(Query_for_list_of_servers);
Thought?
Regards,
--
Fujii Masao
NTT DATA Japan Corporation
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-07-10 01:30 ` Yugo Nagata <[email protected]>
2025-07-10 04:23 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Yugo Nagata @ 2025-07-10 01:30 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Wed, 9 Jul 2025 20:42:42 +0900
Fujii Masao <[email protected]> wrote:
>
>
> On 2025/06/11 13:57, Yugo Nagata wrote:
> > On Wed, 11 Jun 2025 13:33:07 +0900
> > Fujii Masao <[email protected]> wrote:
> >
> >>
> >>
> >> On 2025/06/11 11:49, Yugo Nagata wrote:
> >>> While looking at the thread [1], I've remembered this thread.
> >>> The patches in this thread are partially v18-related, but include
> >>> enhancement or fixes for existing feature, so should they be postponed
> >>> to v19, or should be separated properly to v18 part and other?
> >>>
> >>> [1] https://www.postgresql.org/message-id/70372bdd-4399-4d5b-ab4f-6d4487a4911a%40oss.nttdata.com
> >>
> >> I see these patches more as enhancements to psql tab-completion,
> >> rather than fixes for clear oversights in the original commit.
> >>
> >> For example, if tab-completion for ALTER DEFAULT PRIVILEGES had
> >> completely missed LARGE OBJECTS, that would be an obvious oversight.
> >> But these patches go beyond that kind of issue.
> >>
> >> That said, if others think it's appropriate to include them in v18
> >> for consistency or completeness, I'm fine with that.
> >>
> >> Regarding the 0002 patch:
> >>
> >> - else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
> >> - COMPLETE_WITH("TO");
> >> - else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
> >> - COMPLETE_WITH("FROM");
> >> + else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
> >> + {
> >> + if (TailMatches("FOREIGN", "SERVER"))
> >> + COMPLETE_WITH_QUERY(Query_for_list_of_servers);
> >> + else if (!TailMatches("LARGE", "OBJECT"))
> >> + {
> >> + if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
> >> + COMPLETE_WITH("TO");
> >> + else
> >> + COMPLETE_WITH("FROM");
> >> + }
> >> + }
> >>
> >> Wouldn't this change break the case where "GRANT ... ON TABLE ... <TAB>"
> >> is supposed to complete with "TO"?
> >
> > Sorry, I made a stupid mistake.
> >
> >> + else if (Matches("GRANT/REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
> >
> > This should be "GRANT|REVOKE".
> >
> > I've attached update patches. (There is no change on 0001.)
>
> Thanks for updating the patch! At first I've pushed the 0001 patch.
>
> As for the 0002 patch:
>
> + if (TailMatches("FOREIGN", "SERVER"))
> + COMPLETE_WITH_QUERY(Query_for_list_of_servers);
>
> This part seems not needed, since we already have the following tab-completion code:
>
> /* FOREIGN SERVER */
> else if (TailMatches("FOREIGN", "SERVER"))
> COMPLETE_WITH_QUERY(Query_for_list_of_servers);
>
> Thought?
You're right. I must have overlooked something. I think I saw "TO" being
suggested after "FOREIGN SERVER" when no foreign servers were defined.
The attached patch still prevents "TO/FROM" from being suggested after
"FOREIGN SERVER" in such cases. But perhaps this corner case doesn't really
need to be handled?
Regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 01:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
@ 2025-07-10 04:23 ` Fujii Masao <[email protected]>
2025-07-10 05:11 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Fujii Masao @ 2025-07-10 04:23 UTC (permalink / raw)
To: Yugo Nagata <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/07/10 10:30, Yugo Nagata wrote:
> You're right. I must have overlooked something. I think I saw "TO" being
> suggested after "FOREIGN SERVER" when no foreign servers were defined.
>
> The attached patch still prevents "TO/FROM" from being suggested after
> "FOREIGN SERVER" in such cases.
Thanks for updating the patch!
Based on your patch, I'm thinking of simplifying the code like this:
- else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
- COMPLETE_WITH("TO");
- else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
- COMPLETE_WITH("FROM");
+ else if (Matches("GRANT|REVOKE", MatchAnyN, "ON", MatchAny, MatchAny) &&
+ !TailMatches("FOREIGN", "SERVER") && !TailMatches("LARGE", "OBJECT"))
+ {
+ if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
+ COMPLETE_WITH("TO");
+ else
+ COMPLETE_WITH("FROM");
+ }
> But perhaps this corner case doesn't really
> need to be handled?
Probably I failed to get your point here. Could you clarify what you meant?
Regards,
--
Fujii Masao
NTT DATA Japan Corporation
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 01:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-10 04:23 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-07-10 05:11 ` Yugo Nagata <[email protected]>
2025-07-15 10:07 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Yugo Nagata @ 2025-07-10 05:11 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Thu, 10 Jul 2025 13:23:47 +0900
Fujii Masao <[email protected]> wrote:
>
>
> On 2025/07/10 10:30, Yugo Nagata wrote:
> > You're right. I must have overlooked something. I think I saw "TO" being
> > suggested after "FOREIGN SERVER" when no foreign servers were defined.
> >
> > The attached patch still prevents "TO/FROM" from being suggested after
> > "FOREIGN SERVER" in such cases.
>
> Based on your patch, I'm thinking of simplifying the code like this:
>
> - else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
> - COMPLETE_WITH("TO");
> - else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
> - COMPLETE_WITH("FROM");
> + else if (Matches("GRANT|REVOKE", MatchAnyN, "ON", MatchAny, MatchAny) &&
> + !TailMatches("FOREIGN", "SERVER") && !TailMatches("LARGE", "OBJECT"))
> + {
> + if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
> + COMPLETE_WITH("TO");
> + else
> + COMPLETE_WITH("FROM");
> + }
Thank you for your review!
I agree with your suggestion and have updated the patch accordingly.
>
> > But perhaps this corner case doesn't really
> > need to be handled?
>
> Probably I failed to get your point here. Could you clarify what you meant?
I'm sorry for not explaining it clearly.
Currently, TO or FROM could be suggested immediately after FOREIGN SERVER, but
only when no foreign servers are defined. When foreign servers do exist,
their names are correctly suggested instead, as expected.
The patch fixed the behavior so that TO or FROM are not suggested after FOREIGN SERVER,
even when no foreign servers are defined. However, I've started to wonder if it's worth
fixing such a corner case. What do you think?
Regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 01:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-10 04:23 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 05:11 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
@ 2025-07-15 10:07 ` Fujii Masao <[email protected]>
2025-07-16 07:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
0 siblings, 1 reply; 33+ messages in thread
From: Fujii Masao @ 2025-07-15 10:07 UTC (permalink / raw)
To: Yugo Nagata <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On 2025/07/10 14:11, Yugo Nagata wrote:
> On Thu, 10 Jul 2025 13:23:47 +0900
> Fujii Masao <[email protected]> wrote:
>
>>
>>
>> On 2025/07/10 10:30, Yugo Nagata wrote:
>>> You're right. I must have overlooked something. I think I saw "TO" being
>>> suggested after "FOREIGN SERVER" when no foreign servers were defined.
>>>
>>> The attached patch still prevents "TO/FROM" from being suggested after
>>> "FOREIGN SERVER" in such cases.
>
>>
>> Based on your patch, I'm thinking of simplifying the code like this:
>>
>> - else if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
>> - COMPLETE_WITH("TO");
>> - else if (Matches("REVOKE", MatchAnyN, "ON", MatchAny, MatchAny))
>> - COMPLETE_WITH("FROM");
>> + else if (Matches("GRANT|REVOKE", MatchAnyN, "ON", MatchAny, MatchAny) &&
>> + !TailMatches("FOREIGN", "SERVER") && !TailMatches("LARGE", "OBJECT"))
>> + {
>> + if (Matches("GRANT", MatchAnyN, "ON", MatchAny, MatchAny))
>> + COMPLETE_WITH("TO");
>> + else
>> + COMPLETE_WITH("FROM");
>> + }
>
> Thank you for your review!
> I agree with your suggestion and have updated the patch accordingly.
I've pushed the patch. Thanks!
>>> But perhaps this corner case doesn't really
>>> need to be handled?
>>
>> Probably I failed to get your point here. Could you clarify what you meant?
>
> I'm sorry for not explaining it clearly.
>
> Currently, TO or FROM could be suggested immediately after FOREIGN SERVER, but
> only when no foreign servers are defined. When foreign servers do exist,
> their names are correctly suggested instead, as expected.
>
> The patch fixed the behavior so that TO or FROM are not suggested after FOREIGN SERVER,
> even when no foreign servers are defined. However, I've started to wonder if it's worth
> fixing such a corner case. What do you think?
I think it's worth doing. This issue can lead to unexpected behavior
and is something users might run into. If the fix were overly complex
for a minor issue, it might not be justified. But that's not the case here.
Regards,
--
Fujii Masao
NTT DATA Japan Corporation
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 01:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-10 04:23 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 05:11 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-15 10:07 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-07-16 07:22 ` Yugo Nagata <[email protected]>
0 siblings, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2025-07-16 07:22 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Tue, 15 Jul 2025 19:07:16 +0900
Fujii Masao <[email protected]> wrote:
> I've pushed the patch. Thanks!
Thank you!
>
> >>> But perhaps this corner case doesn't really
> >>> need to be handled?
> >>
> >> Probably I failed to get your point here. Could you clarify what you meant?
> >
> > I'm sorry for not explaining it clearly.
> >
> > Currently, TO or FROM could be suggested immediately after FOREIGN SERVER, but
> > only when no foreign servers are defined. When foreign servers do exist,
> > their names are correctly suggested instead, as expected.
> >
> > The patch fixed the behavior so that TO or FROM are not suggested after FOREIGN SERVER,
> > even when no foreign servers are defined. However, I've started to wonder if it's worth
> > fixing such a corner case. What do you think?
>
> I think it's worth doing. This issue can lead to unexpected behavior
> and is something users might run into. If the fix were overly complex
> for a minor issue, it might not be justified. But that's not the case here.
Thank you for your explanation.
It makes sense to me.
Regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
* Re: Extend ALTER DEFAULT PRIVILEGES for large objects
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
@ 2025-06-11 05:03 ` Yugo Nagata <[email protected]>
1 sibling, 0 replies; 33+ messages in thread
From: Yugo Nagata @ 2025-06-11 05:03 UTC (permalink / raw)
To: Fujii Masao <[email protected]>; +Cc: Laurenz Albe <[email protected]>; Nathan Bossart <[email protected]>; Tom Lane <[email protected]>; pgsql-hackers
On Wed, 11 Jun 2025 13:33:07 +0900
Fujii Masao <[email protected]> wrote:
>
>
> On 2025/06/11 11:49, Yugo Nagata wrote:
> > While looking at the thread [1], I've remembered this thread.
> > The patches in this thread are partially v18-related, but include
> > enhancement or fixes for existing feature, so should they be postponed
> > to v19, or should be separated properly to v18 part and other?
> >
> > [1] https://www.postgresql.org/message-id/70372bdd-4399-4d5b-ab4f-6d4487a4911a%40oss.nttdata.com
>
> I see these patches more as enhancements to psql tab-completion,
> rather than fixes for clear oversights in the original commit.
>
> For example, if tab-completion for ALTER DEFAULT PRIVILEGES had
> completely missed LARGE OBJECTS, that would be an obvious oversight.
> But these patches go beyond that kind of issue.
Thank you for your clarification. I agreed.
Best regards,
Yugo Nagata
--
Yugo Nagata <[email protected]>
^ permalink raw reply [nested|flat] 33+ messages in thread
end of thread, other threads:[~2025-07-16 07:22 UTC | newest]
Thread overview: 33+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2022-10-26 21:14 [PATCH v5 12/15] hio: Use ExtendBufferedRelBy() Andres Freund <[email protected]>
2022-10-26 21:14 [PATCH v4 12/15] hio: Use ExtendBufferedRelBy() Andres Freund <[email protected]>
2022-10-26 21:14 [PATCH v5 11/14] hio: Use ExtendBufferedRelBy() Andres Freund <[email protected]>
2023-03-29 01:39 [PATCH v7 12/14] hio: Use ExtendBufferedRelBy() Andres Freund <[email protected]>
2023-03-29 01:39 [PATCH v6 14/17] hio: Use ExtendBufferedRelBy() Andres Freund <[email protected]>
2023-06-26 08:05 [PATCH v2 2/7] Row pattern recognition patch (parse/analysis). Tatsuo Ishii <[email protected]>
2024-03-08 08:43 [PATCH v2] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-03-08 08:43 [PATCH v3] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-03-08 08:43 [PATCH] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-03-08 08:43 [PATCH] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-03-08 08:43 [PATCH v3] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-03-08 08:43 [PATCH v2] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-03-08 08:43 [PATCH v5] Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2024-09-13 07:18 Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-01-22 12:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Laurenz Albe <[email protected]>
2025-01-23 10:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-01 17:35 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-03 14:04 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-04-03 15:21 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 10:18 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-04 14:47 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Nathan Bossart <[email protected]>
2025-04-04 15:10 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-04-08 03:28 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo NAGATA <[email protected]>
2025-06-11 02:49 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 04:33 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-06-11 04:57 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-09 11:42 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 01:30 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-10 04:23 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-10 05:11 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-07-15 10:07 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Fujii Masao <[email protected]>
2025-07-16 07:22 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
2025-06-11 05:03 ` Re: Extend ALTER DEFAULT PRIVILEGES for large objects Yugo Nagata <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox