public inbox for [email protected]
help / color / mirror / Atom feed[PATCH v1 3/6] Use dlist for syncrep queue.
10+ messages / 5 participants
[nested] [flat]
* [PATCH v1 3/6] Use dlist for syncrep queue.
@ 2020-02-19 20:33 Andres Freund <[email protected]>
0 siblings, 0 replies; 10+ messages in thread
From: Andres Freund @ 2020-02-19 20:33 UTC (permalink / raw)
---
src/include/replication/walsender_private.h | 3 +-
src/include/storage/proc.h | 2 +-
src/backend/replication/syncrep.c | 89 +++++++++------------
src/backend/replication/walsender.c | 2 +-
src/backend/storage/lmgr/proc.c | 2 +-
5 files changed, 41 insertions(+), 57 deletions(-)
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 366828f0a47..c2f946c5c48 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -13,6 +13,7 @@
#define _WALSENDER_PRIVATE_H
#include "access/xlog.h"
+#include "lib/ilist.h"
#include "nodes/nodes.h"
#include "replication/syncrep.h"
#include "storage/latch.h"
@@ -96,7 +97,7 @@ typedef struct
* Synchronous replication queue with one queue per request type.
* Protected by SyncRepLock.
*/
- SHM_QUEUE SyncRepQueue[NUM_SYNC_REP_WAIT_MODE];
+ dlist_head SyncRepQueue[NUM_SYNC_REP_WAIT_MODE];
/*
* Current location of the head of the queue. All waiters should have a
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 2ba37f250de..de222f96681 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -150,7 +150,7 @@ struct PGPROC
*/
XLogRecPtr waitLSN; /* waiting for this LSN or higher */
int syncRepState; /* wait state for sync rep */
- SHM_QUEUE syncRepLinks; /* list link if process is in syncrep queue */
+ dlist_node syncRepLinks; /* list link if process is in syncrep queue */
/*
* All PROCLOCK objects for locks held or awaited by this backend are
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index c284103b548..402d3e41fc6 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -168,7 +168,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
if (!SyncRepRequested())
return;
- Assert(SHMQueueIsDetached(&(MyProc->syncRepLinks)));
+ Assert(dlist_node_is_detached(&MyProc->syncRepLinks));
Assert(WalSndCtl != NULL);
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
@@ -304,7 +304,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
* assertions, but better safe than sorry).
*/
pg_read_barrier();
- Assert(SHMQueueIsDetached(&(MyProc->syncRepLinks)));
+ Assert(dlist_node_is_detached(&MyProc->syncRepLinks));
MyProc->syncRepState = SYNC_REP_NOT_WAITING;
MyProc->waitLSN = 0;
@@ -325,31 +325,32 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
static void
SyncRepQueueInsert(int mode)
{
- PGPROC *proc;
+ dlist_head *queue;
+ dlist_iter iter;
Assert(mode >= 0 && mode < NUM_SYNC_REP_WAIT_MODE);
- proc = (PGPROC *) SHMQueuePrev(&(WalSndCtl->SyncRepQueue[mode]),
- &(WalSndCtl->SyncRepQueue[mode]),
- offsetof(PGPROC, syncRepLinks));
+ queue = &WalSndCtl->SyncRepQueue[mode];
- while (proc)
+ dlist_reverse_foreach(iter, queue)
{
+ PGPROC *proc = dlist_container(PGPROC, syncRepLinks, iter.cur);
+
/*
- * Stop at the queue element that we should after to ensure the queue
- * is ordered by LSN.
+ * Stop at the queue element that we should insert after to ensure the
+ * queue is ordered by LSN.
*/
if (proc->waitLSN < MyProc->waitLSN)
- break;
-
- proc = (PGPROC *) SHMQueuePrev(&(WalSndCtl->SyncRepQueue[mode]),
- &(proc->syncRepLinks),
- offsetof(PGPROC, syncRepLinks));
+ {
+ dlist_insert_after(&proc->syncRepLinks, &MyProc->syncRepLinks);
+ return;
+ }
}
- if (proc)
- SHMQueueInsertAfter(&(proc->syncRepLinks), &(MyProc->syncRepLinks));
- else
- SHMQueueInsertAfter(&(WalSndCtl->SyncRepQueue[mode]), &(MyProc->syncRepLinks));
+ /*
+ * If we get here, the list was either empty, or this process needs to be
+ * at the head.
+ */
+ dlist_push_head(queue, &MyProc->syncRepLinks);
}
/*
@@ -359,8 +360,8 @@ static void
SyncRepCancelWait(void)
{
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
- if (!SHMQueueIsDetached(&(MyProc->syncRepLinks)))
- SHMQueueDelete(&(MyProc->syncRepLinks));
+ if (!dlist_node_is_detached(&MyProc->syncRepLinks))
+ dlist_delete_thoroughly(&MyProc->syncRepLinks);
MyProc->syncRepState = SYNC_REP_NOT_WAITING;
LWLockRelease(SyncRepLock);
}
@@ -372,13 +373,13 @@ SyncRepCleanupAtProcExit(void)
* First check if we are removed from the queue without the lock to not
* slow down backend exit.
*/
- if (!SHMQueueIsDetached(&(MyProc->syncRepLinks)))
+ if (!dlist_node_is_detached(&MyProc->syncRepLinks))
{
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
/* maybe we have just been removed, so recheck */
- if (!SHMQueueIsDetached(&(MyProc->syncRepLinks)))
- SHMQueueDelete(&(MyProc->syncRepLinks));
+ if (!dlist_node_is_detached(&MyProc->syncRepLinks))
+ dlist_delete_thoroughly(&MyProc->syncRepLinks);
LWLockRelease(SyncRepLock);
}
@@ -1011,20 +1012,17 @@ static int
SyncRepWakeQueue(bool all, int mode)
{
volatile WalSndCtlData *walsndctl = WalSndCtl;
- PGPROC *proc = NULL;
- PGPROC *thisproc = NULL;
int numprocs = 0;
+ dlist_mutable_iter iter;
Assert(mode >= 0 && mode < NUM_SYNC_REP_WAIT_MODE);
Assert(LWLockHeldByMeInMode(SyncRepLock, LW_EXCLUSIVE));
Assert(SyncRepQueueIsOrderedByLSN(mode));
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(WalSndCtl->SyncRepQueue[mode]),
- offsetof(PGPROC, syncRepLinks));
-
- while (proc)
+ dlist_foreach_modify(iter, &WalSndCtl->SyncRepQueue[mode])
{
+ PGPROC *proc = dlist_container(PGPROC, syncRepLinks, iter.cur);
+
/*
* Assume the queue is ordered by LSN
*/
@@ -1032,18 +1030,9 @@ SyncRepWakeQueue(bool all, int mode)
return numprocs;
/*
- * Move to next proc, so we can delete thisproc from the queue.
- * thisproc is valid, proc may be NULL after this.
+ * Remove from queue.
*/
- thisproc = proc;
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(proc->syncRepLinks),
- offsetof(PGPROC, syncRepLinks));
-
- /*
- * Remove thisproc from queue.
- */
- SHMQueueDelete(&(thisproc->syncRepLinks));
+ dlist_delete_thoroughly(&proc->syncRepLinks);
/*
* SyncRepWaitForLSN() reads syncRepState without holding the lock, so
@@ -1056,12 +1045,12 @@ SyncRepWakeQueue(bool all, int mode)
* Set state to complete; see SyncRepWaitForLSN() for discussion of
* the various states.
*/
- thisproc->syncRepState = SYNC_REP_WAIT_COMPLETE;
+ proc->syncRepState = SYNC_REP_WAIT_COMPLETE;
/*
* Wake only when we have set state and removed from queue.
*/
- SetLatch(&(thisproc->procLatch));
+ SetLatch(&(proc->procLatch));
numprocs++;
}
@@ -1115,19 +1104,17 @@ SyncRepUpdateSyncStandbysDefined(void)
static bool
SyncRepQueueIsOrderedByLSN(int mode)
{
- PGPROC *proc = NULL;
XLogRecPtr lastLSN;
+ dlist_iter iter;
Assert(mode >= 0 && mode < NUM_SYNC_REP_WAIT_MODE);
lastLSN = 0;
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(WalSndCtl->SyncRepQueue[mode]),
- offsetof(PGPROC, syncRepLinks));
-
- while (proc)
+ dlist_foreach(iter, &WalSndCtl->SyncRepQueue[mode])
{
+ PGPROC *proc = dlist_container(PGPROC, syncRepLinks, iter.cur);
+
/*
* Check the queue is ordered by LSN and that multiple procs don't
* have matching LSNs
@@ -1136,10 +1123,6 @@ SyncRepQueueIsOrderedByLSN(int mode)
return false;
lastLSN = proc->waitLSN;
-
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(proc->syncRepLinks),
- offsetof(PGPROC, syncRepLinks));
}
return true;
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index abb533b9d03..2d2a0ceb503 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3021,7 +3021,7 @@ WalSndShmemInit(void)
MemSet(WalSndCtl, 0, WalSndShmemSize());
for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++)
- SHMQueueInit(&(WalSndCtl->SyncRepQueue[i]));
+ dlist_init(&(WalSndCtl->SyncRepQueue[i]));
for (i = 0; i < max_wal_senders; i++)
{
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 5a157fc07d8..a4c338d7aff 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -415,7 +415,7 @@ InitProcess(void)
/* Initialize fields for sync rep */
MyProc->waitLSN = 0;
MyProc->syncRepState = SYNC_REP_NOT_WAITING;
- SHMQueueElemInit(&(MyProc->syncRepLinks));
+ dlist_node_init(&MyProc->syncRepLinks);
/* Initialize fields for group XID clearing. */
MyProc->procArrayGroupMember = false;
--
2.25.0.114.g5b0ca878e0
--jukqbc7dv5ymsafw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v1-0004-Use-dlists-for-predicate-locking.patch"
^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v2 4/9] Use dlist for syncrep queue
@ 2022-11-19 23:18 Andres Freund <[email protected]>
0 siblings, 0 replies; 10+ messages in thread
From: Andres Freund @ 2022-11-19 23:18 UTC (permalink / raw)
---
src/include/replication/walsender_private.h | 3 +-
src/include/storage/proc.h | 2 +-
src/backend/replication/syncrep.c | 89 +++++++++------------
src/backend/replication/walsender.c | 2 +-
src/backend/storage/lmgr/proc.c | 2 +-
5 files changed, 41 insertions(+), 57 deletions(-)
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 7897c74589e..db801e9f5cf 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -13,6 +13,7 @@
#define _WALSENDER_PRIVATE_H
#include "access/xlog.h"
+#include "lib/ilist.h"
#include "nodes/nodes.h"
#include "replication/syncrep.h"
#include "storage/latch.h"
@@ -89,7 +90,7 @@ typedef struct
* Synchronous replication queue with one queue per request type.
* Protected by SyncRepLock.
*/
- SHM_QUEUE SyncRepQueue[NUM_SYNC_REP_WAIT_MODE];
+ dlist_head SyncRepQueue[NUM_SYNC_REP_WAIT_MODE];
/*
* Current location of the head of the queue. All waiters should have a
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 7005770da79..ba9bf1c9508 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -248,7 +248,7 @@ struct PGPROC
*/
XLogRecPtr waitLSN; /* waiting for this LSN or higher */
int syncRepState; /* wait state for sync rep */
- SHM_QUEUE syncRepLinks; /* list link if process is in syncrep queue */
+ dlist_node syncRepLinks; /* list link if process is in syncrep queue */
/*
* All PROCLOCK objects for locks held or awaited by this backend are
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 1a022b11a06..2c2f1082e97 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -182,7 +182,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
else
mode = Min(SyncRepWaitMode, SYNC_REP_WAIT_FLUSH);
- Assert(SHMQueueIsDetached(&(MyProc->syncRepLinks)));
+ Assert(dlist_node_is_detached(&MyProc->syncRepLinks));
Assert(WalSndCtl != NULL);
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
@@ -318,7 +318,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
* assertions, but better safe than sorry).
*/
pg_read_barrier();
- Assert(SHMQueueIsDetached(&(MyProc->syncRepLinks)));
+ Assert(dlist_node_is_detached(&MyProc->syncRepLinks));
MyProc->syncRepState = SYNC_REP_NOT_WAITING;
MyProc->waitLSN = 0;
@@ -339,31 +339,32 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
static void
SyncRepQueueInsert(int mode)
{
- PGPROC *proc;
+ dlist_head *queue;
+ dlist_iter iter;
Assert(mode >= 0 && mode < NUM_SYNC_REP_WAIT_MODE);
- proc = (PGPROC *) SHMQueuePrev(&(WalSndCtl->SyncRepQueue[mode]),
- &(WalSndCtl->SyncRepQueue[mode]),
- offsetof(PGPROC, syncRepLinks));
+ queue = &WalSndCtl->SyncRepQueue[mode];
- while (proc)
+ dlist_reverse_foreach(iter, queue)
{
+ PGPROC *proc = dlist_container(PGPROC, syncRepLinks, iter.cur);
+
/*
- * Stop at the queue element that we should after to ensure the queue
- * is ordered by LSN.
+ * Stop at the queue element that we should insert after to ensure the
+ * queue is ordered by LSN.
*/
if (proc->waitLSN < MyProc->waitLSN)
- break;
-
- proc = (PGPROC *) SHMQueuePrev(&(WalSndCtl->SyncRepQueue[mode]),
- &(proc->syncRepLinks),
- offsetof(PGPROC, syncRepLinks));
+ {
+ dlist_insert_after(&proc->syncRepLinks, &MyProc->syncRepLinks);
+ return;
+ }
}
- if (proc)
- SHMQueueInsertAfter(&(proc->syncRepLinks), &(MyProc->syncRepLinks));
- else
- SHMQueueInsertAfter(&(WalSndCtl->SyncRepQueue[mode]), &(MyProc->syncRepLinks));
+ /*
+ * If we get here, the list was either empty, or this process needs to be
+ * at the head.
+ */
+ dlist_push_head(queue, &MyProc->syncRepLinks);
}
/*
@@ -373,8 +374,8 @@ static void
SyncRepCancelWait(void)
{
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
- if (!SHMQueueIsDetached(&(MyProc->syncRepLinks)))
- SHMQueueDelete(&(MyProc->syncRepLinks));
+ if (!dlist_node_is_detached(&MyProc->syncRepLinks))
+ dlist_delete_thoroughly(&MyProc->syncRepLinks);
MyProc->syncRepState = SYNC_REP_NOT_WAITING;
LWLockRelease(SyncRepLock);
}
@@ -386,13 +387,13 @@ SyncRepCleanupAtProcExit(void)
* First check if we are removed from the queue without the lock to not
* slow down backend exit.
*/
- if (!SHMQueueIsDetached(&(MyProc->syncRepLinks)))
+ if (!dlist_node_is_detached(&MyProc->syncRepLinks))
{
LWLockAcquire(SyncRepLock, LW_EXCLUSIVE);
/* maybe we have just been removed, so recheck */
- if (!SHMQueueIsDetached(&(MyProc->syncRepLinks)))
- SHMQueueDelete(&(MyProc->syncRepLinks));
+ if (!dlist_node_is_detached(&MyProc->syncRepLinks))
+ dlist_delete_thoroughly(&MyProc->syncRepLinks);
LWLockRelease(SyncRepLock);
}
@@ -879,20 +880,17 @@ static int
SyncRepWakeQueue(bool all, int mode)
{
volatile WalSndCtlData *walsndctl = WalSndCtl;
- PGPROC *proc = NULL;
- PGPROC *thisproc = NULL;
int numprocs = 0;
+ dlist_mutable_iter iter;
Assert(mode >= 0 && mode < NUM_SYNC_REP_WAIT_MODE);
Assert(LWLockHeldByMeInMode(SyncRepLock, LW_EXCLUSIVE));
Assert(SyncRepQueueIsOrderedByLSN(mode));
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(WalSndCtl->SyncRepQueue[mode]),
- offsetof(PGPROC, syncRepLinks));
-
- while (proc)
+ dlist_foreach_modify(iter, &WalSndCtl->SyncRepQueue[mode])
{
+ PGPROC *proc = dlist_container(PGPROC, syncRepLinks, iter.cur);
+
/*
* Assume the queue is ordered by LSN
*/
@@ -900,18 +898,9 @@ SyncRepWakeQueue(bool all, int mode)
return numprocs;
/*
- * Move to next proc, so we can delete thisproc from the queue.
- * thisproc is valid, proc may be NULL after this.
+ * Remove from queue.
*/
- thisproc = proc;
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(proc->syncRepLinks),
- offsetof(PGPROC, syncRepLinks));
-
- /*
- * Remove thisproc from queue.
- */
- SHMQueueDelete(&(thisproc->syncRepLinks));
+ dlist_delete_thoroughly(&proc->syncRepLinks);
/*
* SyncRepWaitForLSN() reads syncRepState without holding the lock, so
@@ -924,12 +913,12 @@ SyncRepWakeQueue(bool all, int mode)
* Set state to complete; see SyncRepWaitForLSN() for discussion of
* the various states.
*/
- thisproc->syncRepState = SYNC_REP_WAIT_COMPLETE;
+ proc->syncRepState = SYNC_REP_WAIT_COMPLETE;
/*
* Wake only when we have set state and removed from queue.
*/
- SetLatch(&(thisproc->procLatch));
+ SetLatch(&(proc->procLatch));
numprocs++;
}
@@ -983,19 +972,17 @@ SyncRepUpdateSyncStandbysDefined(void)
static bool
SyncRepQueueIsOrderedByLSN(int mode)
{
- PGPROC *proc = NULL;
XLogRecPtr lastLSN;
+ dlist_iter iter;
Assert(mode >= 0 && mode < NUM_SYNC_REP_WAIT_MODE);
lastLSN = 0;
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(WalSndCtl->SyncRepQueue[mode]),
- offsetof(PGPROC, syncRepLinks));
-
- while (proc)
+ dlist_foreach(iter, &WalSndCtl->SyncRepQueue[mode])
{
+ PGPROC *proc = dlist_container(PGPROC, syncRepLinks, iter.cur);
+
/*
* Check the queue is ordered by LSN and that multiple procs don't
* have matching LSNs
@@ -1004,10 +991,6 @@ SyncRepQueueIsOrderedByLSN(int mode)
return false;
lastLSN = proc->waitLSN;
-
- proc = (PGPROC *) SHMQueueNext(&(WalSndCtl->SyncRepQueue[mode]),
- &(proc->syncRepLinks),
- offsetof(PGPROC, syncRepLinks));
}
return true;
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index a81ef6a2014..b5a40d2c439 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3270,7 +3270,7 @@ WalSndShmemInit(void)
MemSet(WalSndCtl, 0, WalSndShmemSize());
for (i = 0; i < NUM_SYNC_REP_WAIT_MODE; i++)
- SHMQueueInit(&(WalSndCtl->SyncRepQueue[i]));
+ dlist_init(&(WalSndCtl->SyncRepQueue[i]));
for (i = 0; i < max_wal_senders; i++)
{
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 5ffbd7b8195..39c6685f467 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -410,7 +410,7 @@ InitProcess(void)
/* Initialize fields for sync rep */
MyProc->waitLSN = 0;
MyProc->syncRepState = SYNC_REP_NOT_WAITING;
- SHMQueueElemInit(&(MyProc->syncRepLinks));
+ dlist_node_init(&MyProc->syncRepLinks);
/* Initialize fields for group XID clearing. */
MyProc->procArrayGroupMember = false;
--
2.38.0
--boiclxjzuujo3uzo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0005-Use-dlists-for-predicate-locking.patch"
^ permalink raw reply [nested|flat] 10+ messages in thread
* [PATCH v4 02/19] Remove unused PruneState member rel
@ 2024-03-18 22:59 Melanie Plageman <[email protected]>
0 siblings, 0 replies; 10+ messages in thread
From: Melanie Plageman @ 2024-03-18 22:59 UTC (permalink / raw)
PruneState->rel is no longer being used, so just remove it.
---
src/backend/access/heap/pruneheap.c | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index b5895406ec2..08cb2a6f533 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -29,8 +29,6 @@
/* Working data for heap_page_prune and subroutines */
typedef struct
{
- Relation rel;
-
/* tuple visibility test, initialized for the relation */
GlobalVisState *vistest;
/* whether or not dead items can be set LP_UNUSED during pruning */
@@ -235,7 +233,6 @@ heap_page_prune(Relation relation, Buffer buffer,
* initialize the rest of our working state.
*/
prstate.new_prune_xid = InvalidTransactionId;
- prstate.rel = relation;
prstate.vistest = vistest;
prstate.mark_unused_now = mark_unused_now;
prstate.snapshotConflictHorizon = InvalidTransactionId;
@@ -251,7 +248,7 @@ heap_page_prune(Relation relation, Buffer buffer,
presult->nnewlpdead = 0;
maxoff = PageGetMaxOffsetNumber(page);
- tup.t_tableOid = RelationGetRelid(prstate.rel);
+ tup.t_tableOid = RelationGetRelid(relation);
/*
* Determine HTSV for all tuples.
--
2.40.1
--tez7m2a73jtztiij
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-lazy_scan_prune-tests-tuple-vis-with-GlobalVisTes.patch"
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-01-08 06:16 jian he <[email protected]>
0 siblings, 2 replies; 10+ messages in thread
From: jian he @ 2026-01-08 06:16 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Jul 31, 2025 at 2:24 AM Corey Huinker <[email protected]> wrote:
>
>>
>> Besides, this does nothing you haven't been able to do for
>> decades with expression indexes.
>
>
> What I'm hoping for with this feature is a smoother transition when people start introducing virtual columns. If there was already an index on that expression, and some queries start using the new virtual column (with the same expression), will that cause those queries to miss the index we already have? If it doesn't, then a customer can roll out the query changes at will and not need to do some cut-over from using the expression to using the virtual column.
>
hi.
I am not sure whether this concern has been addressed, as I am still somewhat
confused by the above paragraph.
As noted earlier, creating an index on a virtual generated column results in a
new index, and that index behaves the same as a regular expression index.
An updated and polished patch is attached. The regress tests are quite verbose
at the moment, since I make it covered all index types (btree, gist, spgist,
hash, gin, and brin).
--
jian
https://www.enterprisedb.com/
Attachments:
[text/x-patch] v7-0001-index-on-virtual-generated-column.patch (76.4K, ../../CACJufxEygt-OkS9qSCshNu+fq4533AA0BCBtboW9Mi3xujp=Uw@mail.gmail.com/2-v7-0001-index-on-virtual-generated-column.patch)
download | inline diff:
From 0047c05725121fcb8e241a9edb4bd2983fd4dad2 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Thu, 8 Jan 2026 14:11:30 +0800
Subject: [PATCH v7 1/1] index on virtual generated column
* All index access methods, including btree, hash, GiST, SP-GiST, GIN, and BRIN, are supported.
* Primary key and unique indexes on virtual generated columns are not supported.
* Exclusion constraints on virtual generated columns are not supported at present and maybe in the future.
* Expression indexes and partial (predicate) indexes on virtual generated columns are currently unsupported.
* Indexes with INCLUDED columns cannot reference virtual generated columns.
* An index on a virtual generated column such as (b INT GENERATED ALWAYS AS (a) VIRTUAL)
is effectively equivalent to an index on column a.
* Internally, indexes on virtual generated columns are implemented as expression
indexes. For example, an index on ``(b INT GENERATED ALWAYS AS (a * 2) VIRTUAL)``
is internally represented as an expression index on ``(a * 2)``.
* Additional tests have been added to the pageinspect module to verify index
contents for virtual generated columns.
* To support ALTER TABLE ... SET EXPRESSION, the pg_index catalog records the
the attribute number of the virtual generated column, this is need for indexes
rebuilt.
ALTER COLUMN SET DATA TYPE triggers table rewrite too; therefore, tracking the
attribute number of the virtual generated column referenced by an index is
really needed.
* if the partitioned table and partition both have an index, then the index over the virtual
generated column should be the same expression. For example, the following last
command should error out.
CREATE TABLE parted (b integer,c integer,a integer GENERATED ALWAYS AS (c+1)) PARTITION BY RANGE (b);
CREATE TABLE part (b integer,c integer,a integer GENERATED ALWAYS AS (c));
create index on part(a);
create index on parted(a);
alter table parted ATTACH partition part for values from (1) to (10);
discussion: https://postgr.es/m/CACJufxGao-cypdNhifHAdt8jHfK6-HX=tRBovBkgRuxw063GaA@mail.gmail.com
discussion: https://postgr.es/m/CACJufxGgkH0PyyqP6ggqcEWHxZzmkV=puY8ad=s8kisss9MAwg@mail.gmail.com
related thread: https://postgr.es/m/[email protected]
commitfest: https://commitfest.postgresql.org/patch/5667
---
contrib/pageinspect/expected/btree.out | 11 +
contrib/pageinspect/sql/btree.sql | 10 +
doc/src/sgml/catalogs.sgml | 15 +
src/backend/catalog/index.c | 85 +++++
src/backend/commands/indexcmds.c | 310 +++++++++++++---
src/backend/commands/tablecmds.c | 47 ++-
src/backend/executor/execIndexing.c | 12 +
src/backend/executor/execPartition.c | 5 +
src/backend/parser/parse_utilcmd.c | 29 +-
src/backend/utils/adt/ruleutils.c | 34 +-
src/include/catalog/index.h | 6 +
src/include/catalog/pg_index.h | 3 +
src/include/nodes/execnodes.h | 8 +
src/test/regress/expected/alter_table.out | 20 ++
.../regress/expected/collate.icu.utf8.out | 13 +
src/test/regress/expected/fast_default.out | 8 +
.../regress/expected/generated_virtual.out | 334 +++++++++++++++++-
src/test/regress/expected/indexing.out | 45 +++
src/test/regress/sql/alter_table.sql | 9 +
src/test/regress/sql/collate.icu.utf8.sql | 8 +
src/test/regress/sql/fast_default.sql | 6 +
src/test/regress/sql/generated_virtual.sql | 146 +++++++-
src/test/regress/sql/indexing.sql | 30 ++
23 files changed, 1114 insertions(+), 80 deletions(-)
diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out
index 0aa5d73322f..b7d36f7d047 100644
--- a/contrib/pageinspect/expected/btree.out
+++ b/contrib/pageinspect/expected/btree.out
@@ -183,6 +183,17 @@ tids |
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2));
ERROR: block number 2 is out of range for relation "test1_a_idx"
+---test index over virtual generated column
+CREATE TABLE test4(a int, b int GENERATED ALWAYS AS (a + 1), c text);
+INSERT INTO test4(a,c) VALUES (10,11), (10,11);
+CREATE INDEX test4_b_idx ON test4 USING btree (b);
+CREATE INDEX test4_a_1_idx ON test4 USING btree ((a+1));
+--expect return zero row
+SELECT * FROM bt_page_items('test4_b_idx', 1)
+EXCEPT ALL
+SELECT * FROM bt_page_items('test4_a_1_idx', 1);
+(0 rows)
+
-- Failure when using a non-btree index.
CREATE INDEX test1_a_hash ON test1 USING hash(a);
SELECT bt_metap('test1_a_hash');
diff --git a/contrib/pageinspect/sql/btree.sql b/contrib/pageinspect/sql/btree.sql
index 102ebdefe3c..2670f85f79a 100644
--- a/contrib/pageinspect/sql/btree.sql
+++ b/contrib/pageinspect/sql/btree.sql
@@ -32,6 +32,16 @@ SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0));
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1));
SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2));
+---test index over virtual generated column
+CREATE TABLE test4(a int, b int GENERATED ALWAYS AS (a + 1), c text);
+INSERT INTO test4(a,c) VALUES (10,11), (10,11);
+CREATE INDEX test4_b_idx ON test4 USING btree (b);
+CREATE INDEX test4_a_1_idx ON test4 USING btree ((a+1));
+--expect return zero row
+SELECT * FROM bt_page_items('test4_b_idx', 1)
+EXCEPT ALL
+SELECT * FROM bt_page_items('test4_a_1_idx', 1);
+
-- Failure when using a non-btree index.
CREATE INDEX test1_a_hash ON test1 USING hash(a);
SELECT bt_metap('test1_a_hash');
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2fc63442980..1405ac8424f 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4592,6 +4592,21 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>indattrgenerated</structfield> <type>int2vector</type>
+ (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+ </para>
+ <para>
+ This is an array of <structfield>indnatts</structfield> values that
+ indicate which virtual generated columns this index indexes.
+ For example, a value of <literal>1 3</literal> would mean that the first
+ and the third table columns of this index entries are virtual generated
+ column. A zero in this array indicates that the corresponding index
+ attribute is not virtual generated column reference.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>indexprs</structfield> <type>pg_node_tree</type>
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 43de42ce39e..7b1d8e69c57 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -245,6 +245,16 @@ index_check_primary_key(Relation heapRel,
HeapTuple atttuple;
Form_pg_attribute attform;
+ /*
+ * Unique constraints and primary keys based on virtual generated
+ * columns are not supported. Nevertheless, it is still prudent to
+ * perform the check.
+ */
+ if (indexInfo->ii_IndexAttrGeneratedNumbers[i] != 0)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary keys on virtual generated columns are not supported"));
+
if (attnum == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -582,6 +592,12 @@ UpdateIndexRelation(Oid indexoid,
Relation pg_index;
HeapTuple tuple;
int i;
+ int2vector *indgenkey;
+
+ int16 *colgenerated = palloc_array(int16, indexInfo->ii_NumIndexAttrs);
+
+ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+ colgenerated[i] = indexInfo->ii_IndexAttrGeneratedNumbers[i];
/*
* Copy the index key, opclass, and indoption info into arrays (should we
@@ -593,6 +609,7 @@ UpdateIndexRelation(Oid indexoid,
indcollation = buildoidvector(collationOids, indexInfo->ii_NumIndexKeyAttrs);
indclass = buildoidvector(opclassOids, indexInfo->ii_NumIndexKeyAttrs);
indoption = buildint2vector(coloptions, indexInfo->ii_NumIndexKeyAttrs);
+ indgenkey = buildint2vector(colgenerated, indexInfo->ii_NumIndexAttrs);
/*
* Convert the index expressions (if any) to a text datum
@@ -651,6 +668,7 @@ UpdateIndexRelation(Oid indexoid,
values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
values[Anum_pg_index_indoption - 1] = PointerGetDatum(indoption);
+ values[Anum_pg_index_indattrgenerated - 1] = PointerGetDatum(indgenkey);
values[Anum_pg_index_indexprs - 1] = exprsDatum;
if (exprsDatum == (Datum) 0)
nulls[Anum_pg_index_indexprs - 1] = true;
@@ -1132,6 +1150,28 @@ index_create(Relation heapRelation,
}
}
+ /*
+ * Internally, we convert index of virtual generation column into
+ * an expression index. For example, if column 'b' is defined as
+ * (b INT GENERATED ALWAYS AS (a * 2) VIRTUAL) then index over 'b'
+ * would transformed into an expression index as ((a * 2)). As a
+ * result, the pg_depend refobjsubid does not retain the original
+ * attribute number of the virtual generated column. But rebuild
+ * index need these original virtual generated column. Thus we
+ * need auto dependencies on referenced virtual generated columns.
+ */
+ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+ {
+ if (indexInfo->ii_IndexAttrGeneratedNumbers[i] != 0)
+ {
+ ObjectAddressSubSet(referenced, RelationRelationId,
+ heapRelationId,
+ indexInfo->ii_IndexAttrGeneratedNumbers[i]);
+ add_exact_object_address(&referenced, addrs);
+ have_simple_col = false;
+ }
+ }
+
/*
* If there are no simply-referenced columns, give the index an
* auto dependency on the whole table. In most cases, this will
@@ -1409,6 +1449,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
indexColNames = lappend(indexColNames, NameStr(att->attname));
newInfo->ii_IndexAttrNumbers[i] = oldInfo->ii_IndexAttrNumbers[i];
+ newInfo->ii_IndexAttrGeneratedNumbers[i] = oldInfo->ii_IndexAttrGeneratedNumbers[i];
}
/* Extract opclass options for each attribute */
@@ -2426,9 +2467,12 @@ IndexInfo *
BuildIndexInfo(Relation index)
{
IndexInfo *ii;
+ HeapTuple ht_idx;
Form_pg_index indexStruct = index->rd_index;
int i;
int numAtts;
+ Datum indgenkeyDatum;
+ int2vector *indgenkey;
/* check the number of keys, and copy attr numbers into the IndexInfo */
numAtts = indexStruct->indnatts;
@@ -2452,9 +2496,18 @@ BuildIndexInfo(Relation index)
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
+ ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexStruct->indexrelid));
+ indgenkeyDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
+ Anum_pg_index_indattrgenerated);
+ indgenkey = (int2vector *) DatumGetPointer(indgenkeyDatum);
+
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
+ {
ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+ ii->ii_IndexAttrGeneratedNumbers[i] = indgenkey->values[i];
+ }
+ ReleaseSysCache(ht_idx);
/* fetch exclusion constraint info if any */
if (indexStruct->indisexclusion)
@@ -2516,11 +2569,34 @@ BuildDummyIndexInfo(Relation index)
for (i = 0; i < numAtts; i++)
ii->ii_IndexAttrNumbers[i] = indexStruct->indkey.values[i];
+ /*
+ * Index expressions or predicates are skipped here, see above comments.
+ * If virtual generated columns references another column,
+ * ii_IndexAttrNumbers will set to that referenced column. So do nothing
+ * for ii_IndexAttrGeneratedNumbers here.
+ */
+
/* We ignore the exclusion constraint if any */
return ii;
}
+/*
+ * indexContainVirtual
+ * Return true if this index contains a reference to a virtual generated
+ * column.
+ */
+bool
+indexContainVirtual(const IndexInfo *info)
+{
+ for (int i = 0; i < info->ii_NumIndexAttrs; i++)
+ {
+ if (AttributeNumberIsValid(info->ii_IndexAttrGeneratedNumbers[i]))
+ return true;
+ }
+ return false;
+}
+
/*
* CompareIndexInfo
* Return whether the properties of two indexes (in different tables)
@@ -2568,6 +2644,15 @@ CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
if (attmap->maplen < info2->ii_IndexAttrNumbers[i])
elog(ERROR, "incorrect attribute map");
+ if (AttributeNumberIsValid(info1->ii_IndexAttrGeneratedNumbers[i]) ||
+ AttributeNumberIsValid(info2->ii_IndexAttrGeneratedNumbers[i]))
+ {
+ /* fail if index over virtual generated column does not match */
+ if (attmap->attnums[info2->ii_IndexAttrGeneratedNumbers[i] - 1] !=
+ info1->ii_IndexAttrGeneratedNumbers[i])
+ return false;
+ }
+
/* ignore expressions for now (but check their collation/opfamily) */
if (!(info1->ii_IndexAttrNumbers[i] == InvalidAttrNumber &&
info2->ii_IndexAttrNumbers[i] == InvalidAttrNumber))
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 755dc00c86f..5b677483be2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -53,6 +53,7 @@
#include "parser/parse_utilcmd.h"
#include "partitioning/partdesc.h"
#include "pgstat.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
@@ -90,9 +91,17 @@ static void ComputeIndexAttrs(ParseState *pstate,
bool amcanorder,
bool isconstraint,
bool iswithoutoverlaps,
+ bool is_primary,
Oid ddl_userid,
int ddl_sec_context,
int *ddl_save_nestlevel);
+static void ComputeIndexGeneratedAttrs(ParseState *pstate,
+ IndexInfo *indexInfo,
+ Relation rel,
+ bool is_primary,
+ int attn,
+ int attnum,
+ int location);
static char *ChooseIndexName(const char *tabname, Oid namespaceId,
const List *colnames, const List *exclusionOpNames,
bool primary, bool isconstraint);
@@ -182,6 +191,7 @@ CheckIndexCompatible(Oid oldId,
bool isWithoutOverlaps)
{
bool isconstraint;
+ bool is_primary;
Oid *typeIds;
Oid *collationIds;
Oid *opclassIds;
@@ -214,6 +224,12 @@ CheckIndexCompatible(Oid oldId,
*/
isconstraint = false;
+ /*
+ * We can pretend is_primary = false unconditionally. It only serves to
+ * decide the text of an error message that should never happen for us.
+ */
+ is_primary = false;
+
numberOfAttributes = list_length(attributeList);
Assert(numberOfAttributes > 0);
Assert(numberOfAttributes <= INDEX_MAX_KEYS);
@@ -254,7 +270,7 @@ CheckIndexCompatible(Oid oldId,
coloptions, attributeList,
exclusionOpNames, relationId,
accessMethodName, accessMethodId,
- amcanorder, isconstraint, isWithoutOverlaps, InvalidOid,
+ amcanorder, isconstraint, isWithoutOverlaps, is_primary, InvalidOid,
0, NULL);
/* Get the soon-obsolete pg_index tuple. */
@@ -907,6 +923,27 @@ DefineIndex(ParseState *pstate,
if (stmt->whereClause)
CheckPredicate((Expr *) stmt->whereClause);
+ /* virtual generated column over predicate indexes are not supported */
+ if (RelationGetDescr(rel)->constr &&
+ RelationGetDescr(rel)->constr->has_generated_virtual &&
+ stmt->whereClause)
+ {
+ Bitmapset *indexattrs_pred = NULL;
+ int j = -1;
+
+ pull_varattnos(stmt->whereClause, 1, &indexattrs_pred);
+
+ while ((j = bms_next_member(indexattrs_pred, j)) >= 0)
+ {
+ AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
+
+ if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("partial index on virtual generated columns are not supported"));
+ }
+ }
+
/*
* Parse AM-specific options, convert to text array form, validate.
*/
@@ -944,6 +981,7 @@ DefineIndex(ParseState *pstate,
stmt->excludeOpNames, tableId,
accessMethodName, accessMethodId,
amcanorder, stmt->isconstraint, stmt->iswithoutoverlaps,
+ stmt->primary,
root_save_userid, root_save_sec_context,
&root_save_nestlevel);
@@ -1025,6 +1063,11 @@ DefineIndex(ParseState *pstate,
constraint_type)));
/* Search the index column(s) for a match */
+
+ /*
+ * Unique constraints and primary keys based on virtual generated
+ * columns are not supported. So no need worry about it here.
+ */
for (j = 0; j < indexInfo->ii_NumIndexKeyAttrs; j++)
{
if (key->partattrs[i] == indexInfo->ii_IndexAttrNumbers[j])
@@ -1105,9 +1148,6 @@ DefineIndex(ParseState *pstate,
/*
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
- *
- * Also disallow virtual generated columns in indexes (use expression
- * index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
@@ -1117,26 +1157,14 @@ DefineIndex(ParseState *pstate,
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
-
-
- if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
- ereport(ERROR,
- errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- stmt->primary ?
- errmsg("primary keys on virtual generated columns are not supported") :
- stmt->isconstraint ?
- errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported"));
}
/*
- * Also check for system and generated columns used in expressions or
- * predicates.
+ * Also check for system columns used in expressions or predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
- int j;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1149,24 +1177,6 @@ DefineIndex(ParseState *pstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
-
- /*
- * XXX Virtual generated columns in index expressions or predicates
- * could be supported, but it needs support in
- * RelationGetIndexExpressions() and RelationGetIndexPredicate().
- */
- j = -1;
- while ((j = bms_next_member(indexattrs, j)) >= 0)
- {
- AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
-
- if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- stmt->isconstraint ?
- errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported")));
- }
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
@@ -1310,6 +1320,7 @@ DefineIndex(ParseState *pstate,
bool invalidate_parent = false;
Relation parentIndex;
TupleDesc parentDesc;
+ bool parent_idx_virtual;
/*
* Report the total number of partitions at the start of the
@@ -1355,6 +1366,7 @@ DefineIndex(ParseState *pstate,
*/
parentIndex = index_open(indexRelationId, lockmode);
indexInfo = BuildIndexInfo(parentIndex);
+ parent_idx_virtual = indexContainVirtual(indexInfo);
parentDesc = RelationGetDescr(rel);
@@ -1415,6 +1427,15 @@ DefineIndex(ParseState *pstate,
parentDesc,
false);
+ /*
+ * If parent has an index on a virtual generated column, we
+ * must ensure that the indexed generated expression on the
+ * parent matches that of the child.
+ */
+ if (parent_idx_virtual)
+ check_generated_indexattrs(indexInfo, rel,
+ childrel, attmap, false);
+
foreach(cell, childidxs)
{
Oid cldidxid = lfirst_oid(cell);
@@ -1862,6 +1883,66 @@ CheckPredicate(Expr *predicate)
errmsg("functions in index predicate must be marked IMMUTABLE")));
}
+/*
+ * Verify that the generated expression of the parent matches the child
+ *
+ * indexinfo: the IndexInfo that is associated with relation "rel".
+ * childrel : the relation to be attached to "rel" or the child of "rel".
+ * attmap : Attribute mapping between childrel and rel.
+ * is_attach: is this command of ALTER TABLE ATTACH PARTITION
+ *
+ * Use build_attrmap_by_name(childrel, rel) to build the attmap.
+*/
+void
+check_generated_indexattrs(const IndexInfo *indexinfo,
+ Relation rel,
+ Relation childrel,
+ const AttrMap *attmap,
+ bool is_attach)
+{
+ /* if parent have virtual generated column, child must also have */
+ Assert(rel->rd_att->constr->has_generated_virtual);
+ Assert(childrel->rd_att->constr->has_generated_virtual);
+
+ for (int i = 0; i < indexinfo->ii_NumIndexAttrs; i++)
+ {
+ if (AttributeNumberIsValid(indexinfo->ii_IndexAttrGeneratedNumbers[i]))
+ {
+ Node *node_parent;
+ Node *node_child;
+ bool found_whole_row;
+
+ AttrNumber attno = indexinfo->ii_IndexAttrGeneratedNumbers[i];
+
+ node_parent = build_generation_expression(rel, attno);
+
+ node_parent = map_variable_attnos(node_parent,
+ 1,
+ 0,
+ attmap,
+ InvalidOid, &found_whole_row);
+ if (found_whole_row)
+ elog(ERROR, "Index contains a whole-row table reference");
+
+ node_child = build_generation_expression(childrel,
+ attmap->attnums[attno - 1]);
+
+ if (!equal(node_parent, node_child))
+ ereport(ERROR,
+ errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ is_attach ?
+ errmsg("cannot attach table \"%s\" as partition of partitioned table \"%s\"",
+ RelationGetRelationName(childrel),
+ RelationGetRelationName(rel)) :
+ errmsg("cannot create index on partitioned table \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail("The virtual generated column expression of partitioned table \"%s\" does not match that of table \"%s\"",
+ RelationGetRelationName(rel),
+ RelationGetRelationName(childrel)));
+ }
+ }
+}
+
/*
* Compute per-index-column information, including indexed column numbers
* or index expressions, opclasses and their options. Note, all output vectors
@@ -1887,6 +1968,7 @@ ComputeIndexAttrs(ParseState *pstate,
bool amcanorder,
bool isconstraint,
bool iswithoutoverlaps,
+ bool is_primary,
Oid ddl_userid,
int ddl_sec_context,
int *ddl_save_nestlevel)
@@ -1897,6 +1979,25 @@ ComputeIndexAttrs(ParseState *pstate,
int nkeycols = indexInfo->ii_NumIndexKeyAttrs;
Oid save_userid;
int save_sec_context;
+ List *virtual_generated = NIL;
+ Relation rel = table_open(relId, NoLock);
+ TupleDesc reltupldesc = RelationGetDescr(rel);
+
+ /*
+ * Virtual generated columns are not currently supported in expression
+ * indexes. We therefore collect the virtual generated columns attribute
+ * number for subsequent verification of expression attributes.
+ */
+ if (reltupldesc->constr && reltupldesc->constr->has_generated_virtual)
+ {
+ for (int i = 0; i < reltupldesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(reltupldesc, i);
+
+ if (!attr->attisdropped && attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ virtual_generated = lappend_int(virtual_generated, attr->attnum);
+ }
+ }
/* Allocate space for exclusion operator info, if needed */
if (exclusionOpNames)
@@ -1968,7 +2069,19 @@ ComputeIndexAttrs(ParseState *pstate,
parser_errposition(pstate, attribute->location)));
}
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
- indexInfo->ii_IndexAttrNumbers[attn] = attform->attnum;
+
+ if (attform->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ ComputeIndexGeneratedAttrs(pstate, indexInfo, rel,
+ is_primary,
+ attn,
+ attform->attnum,
+ attribute->location);
+ else
+ {
+ indexInfo->ii_IndexAttrGeneratedNumbers[attn] = 0;
+ indexInfo->ii_IndexAttrNumbers[attn] = attform->attnum;
+ }
+
atttype = attform->atttypid;
attcollation = attform->attcollation;
ReleaseSysCache(atttuple);
@@ -1995,18 +2108,47 @@ ComputeIndexAttrs(ParseState *pstate,
while (IsA(expr, CollateExpr))
expr = (Node *) ((CollateExpr *) expr)->arg;
+ if (!IsA(expr, Var))
+ {
+ Bitmapset *idxattrs = NULL;
+ int j = -1;
+
+ pull_varattnos(expr, 1, &idxattrs);
+ while ((j = bms_next_member(idxattrs, j)) >= 0)
+ {
+ if (list_member_int(virtual_generated, j + FirstLowInvalidHeapAttributeNumber))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("expression index over virtual generated columns are not supported"),
+ parser_errposition(pstate, attribute->location));
+ }
+ }
+
if (IsA(expr, Var) &&
((Var *) expr)->varattno != InvalidAttrNumber)
{
- /*
- * User wrote "(column)" or "(column COLLATE something)".
- * Treat it like simple attribute anyway.
- */
- indexInfo->ii_IndexAttrNumbers[attn] = ((Var *) expr)->varattno;
+ int attnum = ((Var *) expr)->varattno;
+
+ if (list_member_int(virtual_generated, attnum))
+ ComputeIndexGeneratedAttrs(pstate, indexInfo, rel,
+ is_primary,
+ attn,
+ attnum,
+ attribute->location);
+ else
+ {
+ /*
+ * User wrote "(column)" or "(column COLLATE something)".
+ * Treat it like simple attribute anyway.
+ */
+ indexInfo->ii_IndexAttrNumbers[attn] = attnum;
+ indexInfo->ii_IndexAttrGeneratedNumbers[attn] = 0;
+ }
}
else
{
indexInfo->ii_IndexAttrNumbers[attn] = 0; /* marks expression */
+ indexInfo->ii_IndexAttrGeneratedNumbers[attn] = 0;
indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions,
expr);
@@ -2268,6 +2410,92 @@ ComputeIndexAttrs(ParseState *pstate,
attn++;
}
+
+ table_close(rel, NoLock);
+}
+
+/*
+ * Compute IndexInfo fields: ii_IndexAttrNumbers, ii_IndexAttrGeneratedNumbers,
+ * and ii_Expressions.
+ *
+ * pstate: ParseState struct (used only for error reports; pass NULL if not available)
+ * indexInfo: the IndexInfo structure to be built.
+ * rel: the relation on which this IndexInfo is based.
+ * is_primary: indicates whether this index is a primary key.
+ * attn: zero-based indexes of the index key attributes.
+ * attnum: attribute number of the virtual generated column.
+ * location: index key location.
+ */
+static void
+ComputeIndexGeneratedAttrs(ParseState *pstate,
+ IndexInfo *indexInfo, Relation rel,
+ bool is_primary, int attn, int attnum,
+ int location)
+{
+ Node *node;
+
+ if (is_primary)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("primary keys on virtual generated columns are not supported"),
+ parser_errposition(pstate, location));
+
+ if (indexInfo->ii_Unique)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unique constraints on virtual generated columns are not supported"),
+ parser_errposition(pstate, location));
+
+ if (attn >= indexInfo->ii_NumIndexKeyAttrs)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("virtual generated column are not supported in index included columns"),
+ parser_errposition(pstate, location));
+
+ /* Fetch the GENERATED AS expression tree */
+ node = build_generation_expression(rel, attnum);
+
+ /*
+ * if the generation expression just reference another column, then set
+ * ii_IndexAttrNumbers to that column attribute number.
+ */
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (var->varattno < 0)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("index creation on system columns is not supported"),
+ parser_errposition(pstate, location));
+
+ indexInfo->ii_IndexAttrNumbers[attn] = var->varattno;
+ }
+ else
+ {
+ /*
+ * Strip any top-level COLLATE clause in generated expression. This
+ * ensures that we treat "x COLLATE y" and "(x COLLATE y)" alike.
+ */
+ while (IsA(node, CollateExpr))
+ node = (Node *) ((CollateExpr *) node)->arg;
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ Assert(var->varattno > 0);
+ indexInfo->ii_IndexAttrNumbers[attn] = var->varattno;
+ }
+ else
+ {
+ /* mark as expression index */
+ indexInfo->ii_IndexAttrNumbers[attn] = 0;
+ indexInfo->ii_Expressions = lappend(indexInfo->ii_Expressions,
+ node);
+ }
+ }
+ indexInfo->ii_IndexAttrGeneratedNumbers[attn] = attnum;
}
/*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f976c0e5c7e..6cc296b2957 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8699,6 +8699,21 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
ReleaseSysCache(tuple);
+ /*
+ * Find everything that depends on the column (constraints, indexes, etc),
+ * and record enough information to let us recreate the objects after
+ * rewrite.
+ */
+ RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
+
+ /*
+ * Changing virtual geneeration expression does not require table rewrite.
+ * However, if any index is built on top of it, table rewrite is
+ * necessary.
+ */
+ if (tab->changedIndexOids != NIL)
+ rewrite = true;
+
if (rewrite)
{
/*
@@ -8709,13 +8724,6 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
/* make sure we don't conflict with later attribute modifications */
CommandCounterIncrement();
-
- /*
- * Find everything that depends on the column (constraints, indexes,
- * etc), and record enough information to let us recreate the objects
- * after rewrite.
- */
- RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName);
}
/*
@@ -14870,6 +14878,27 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
*/
RememberAllDependentForRebuilding(tab, AT_AlterColumnType, rel, attnum, colName);
+ /*
+ * Tell phase3 do table rewrite if there are any index based on virtual
+ * generated colum.
+ */
+ if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL &&
+ tab->changedIndexOids != NIL)
+ {
+ Relation newrel;
+
+ newrel = table_open(RelationGetRelid(rel), NoLock);
+
+ RelationClearMissing(newrel);
+
+ relation_close(newrel, NoLock);
+
+ /* make sure we don't conflict with later attribute modifications */
+ CommandCounterIncrement();
+
+ tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+ }
+
/*
* Now scan for dependencies of this column on other things. The only
* things we should find are the dependency on the column datatype and
@@ -20699,6 +20728,10 @@ AttachPartitionEnsureIndexes(List **wqueue, Relation rel, Relation attachrel)
attmap = build_attrmap_by_name(RelationGetDescr(attachrel),
RelationGetDescr(rel),
false);
+
+ if (indexContainVirtual(info))
+ check_generated_indexattrs(info, rel, attachrel, attmap, true);
+
constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
/*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 6ae0f959592..06329347a30 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -1039,6 +1039,18 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
for (int attr = 0; attr < indexInfo->ii_NumIndexKeyAttrs; attr++)
{
int keycol = indexInfo->ii_IndexAttrNumbers[attr];
+ int vkeycol = indexInfo->ii_IndexAttrGeneratedNumbers[attr];
+
+ if (vkeycol > 0 &&
+ (bms_is_member(vkeycol - FirstLowInvalidHeapAttributeNumber,
+ updatedCols) ||
+ bms_is_member(vkeycol - FirstLowInvalidHeapAttributeNumber,
+ extraUpdatedCols)))
+ {
+ /* Changed key column -- don't hint for this index */
+ indexInfo->ii_IndexUnchanged = false;
+ return false;
+ }
if (keycol <= 0)
{
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 55bdf5c4835..439d85fedcb 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -538,6 +538,11 @@ IsIndexCompatibleAsArbiter(Relation arbiterIndexRelation,
if (arbiterIndexRelation->rd_index->indkey.values[i] !=
indexRelation->rd_index->indkey.values[i])
return false;
+
+ /*
+ * Unique indexes on virtual generated columns are not supported, no
+ * need to worry about indattrgenerated here.
+ */
}
if (list_difference(RelationGetIndexExpressions(arbiterIndexRelation),
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 652f7538c37..48fd08e428d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1705,6 +1705,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
Form_pg_am amrec;
oidvector *indcollation;
oidvector *indclass;
+ int2vector *indgenkey;
IndexStmt *index;
List *indexprs;
ListCell *indexpr_item;
@@ -1712,6 +1713,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
int keyno;
Oid keycoltype;
Datum datum;
+ Datum indgenkeyDatum;
bool isnull;
if (constraintOid)
@@ -1747,6 +1749,11 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
datum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx, Anum_pg_index_indclass);
indclass = (oidvector *) DatumGetPointer(datum);
+ /* Extract indattrgenerated from the pg_index tuple */
+ indgenkeyDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
+ Anum_pg_index_indattrgenerated);
+ indgenkey = (int2vector *) DatumGetPointer(indgenkeyDatum);
+
/* Begin building the IndexStmt */
index = makeNode(IndexStmt);
index->relation = heapRel;
@@ -1878,13 +1885,26 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
{
IndexElem *iparam;
AttrNumber attnum = idxrec->indkey.values[keyno];
+ AttrNumber gennum = indgenkey->values[keyno];
Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(source_idx),
keyno);
int16 opt = source_idx->rd_indoption[keyno];
iparam = makeNode(IndexElem);
- if (AttributeNumberIsValid(attnum))
+ if (AttributeNumberIsValid(gennum))
+ {
+ /*
+ * An index on a virtual generated column was converted into an
+ * expression index, but here we need the original attribute
+ * number.
+ */
+ keycoltype = get_atttype(indrelid, gennum);
+
+ iparam->name = get_attname(indrelid, gennum, false);;
+ iparam->expr = NULL;
+ }
+ else if (AttributeNumberIsValid(attnum))
{
/* Simple index column */
char *attname;
@@ -1977,6 +1997,10 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
iparam = makeNode(IndexElem);
+ /*
+ * INCLUDED COLUMNS based on virtual generated columns are not
+ * supported. So no need worry about it here.
+ */
if (AttributeNumberIsValid(attnum))
{
/* Simple index column */
@@ -2528,6 +2552,9 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
* We shouldn't see attnum == 0 here, since we already rejected
* expression indexes. If we do, SystemAttributeDefinition will
* throw an error.
+ *
+ * Unique constraints and primary keys based on virtual generated
+ * columns are not supported. So no need worry about it here.
*/
if (attnum > 0)
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 416f1a21ae4..2fd1b334a08 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1290,9 +1290,11 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
Datum indcollDatum;
Datum indclassDatum;
Datum indoptionDatum;
+ Datum indgenkeyDatum;
oidvector *indcollation;
oidvector *indclass;
int2vector *indoption;
+ int2vector *indgenkey;
StringInfoData buf;
char *str;
char *sep;
@@ -1325,6 +1327,10 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
Anum_pg_index_indoption);
indoption = (int2vector *) DatumGetPointer(indoptionDatum);
+ indgenkeyDatum = SysCacheGetAttrNotNull(INDEXRELID, ht_idx,
+ Anum_pg_index_indattrgenerated);
+ indgenkey = (int2vector *) DatumGetPointer(indgenkeyDatum);
+
/*
* Fetch the pg_class tuple of the index relation
*/
@@ -1398,6 +1404,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
for (keyno = 0; keyno < idxrec->indnatts; keyno++)
{
AttrNumber attnum = idxrec->indkey.values[keyno];
+ AttrNumber gennum = indgenkey->values[keyno];
Oid keycoltype;
Oid keycolcollation;
@@ -1418,7 +1425,32 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
appendStringInfoString(&buf, sep);
sep = ", ";
- if (attnum != 0)
+ /*
+ * We must first check indexes on virtual generated columns, and then
+ * simple index columns, since a virtual generated column can
+ * reference another regular column.
+ */
+ if (AttributeNumberIsValid(gennum))
+ {
+ char *virtual_attname;
+ int32 geneycoltypmod;
+
+ virtual_attname = get_attname(indrelid, gennum, false);
+ if (!colno || colno == keyno + 1)
+ appendStringInfoString(&buf, quote_identifier(virtual_attname));
+
+ get_atttypetypmodcoll(indrelid, gennum,
+ &keycoltype, &geneycoltypmod,
+ &keycolcollation);
+
+ /*
+ * The virtual generated column has already been printed, so its
+ * generation expression should not be emitted again.
+ */
+ if (!AttributeNumberIsValid(attnum))
+ indexpr_item = lnext(indexprs, indexpr_item);
+ }
+ else if (attnum != 0)
{
/* Simple index column */
char *attname;
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index b259c4141ed..e9519a3cafd 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -126,6 +126,7 @@ extern IndexInfo *BuildIndexInfo(Relation index);
extern IndexInfo *BuildDummyIndexInfo(Relation index);
+extern bool indexContainVirtual(const IndexInfo *info);
extern bool CompareIndexInfo(const IndexInfo *info1, const IndexInfo *info2,
const Oid *collations1, const Oid *collations2,
const Oid *opfamilies1, const Oid *opfamilies2,
@@ -175,6 +176,11 @@ extern void RestoreReindexState(const void *reindexstate);
extern void IndexSetParentIndex(Relation partitionIdx, Oid parentOid);
+extern void check_generated_indexattrs(const IndexInfo *rel_idx_info,
+ Relation rel,
+ Relation childrel,
+ const AttrMap *attmap,
+ bool is_attach);
/*
* itemptr_encode - Encode ItemPointer as int64/int8
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index 02c99d70faf..e3097106d8e 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -54,6 +54,9 @@ CATALOG(pg_index,2610,IndexRelationId) BKI_SCHEMA_MACRO
oidvector indclass BKI_LOOKUP(pg_opclass) BKI_FORCE_NOT_NULL; /* opclass identifiers */
int2vector indoption BKI_FORCE_NOT_NULL; /* per-column flags
* (AM-specific meanings) */
+ int2vector indattrgenerated BKI_FORCE_NOT_NULL; /* the attribute of
+ * virtual generated
+ * column? */
pg_node_tree indexprs; /* expression trees for index attributes that
* are not simple column references; one for
* each zero entry in indkey[] */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 02265456978..58079e49f7a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -174,6 +174,14 @@ typedef struct IndexInfo
*/
AttrNumber ii_IndexAttrNumbers[INDEX_MAX_KEYS];
+ /*
+ * Underlying-rel virtual generated attribute numbers used as keys. A
+ * value of zero indicates that the index key does not reference a virtual
+ * generated column. Note: Virtual generated columns cannot be used in
+ * INCLUDED index columns.
+ */
+ AttrNumber ii_IndexAttrGeneratedNumbers[INDEX_MAX_KEYS];
+
/* expr trees for expression entries, or NIL if none */
List *ii_Expressions; /* list of Expr */
/* exec state for expressions, or NIL if none */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index ac1a7345d0f..7e530357345 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -119,6 +119,26 @@ HINT: Alter statistics on table column instead.
ALTER INDEX attmp_idx ALTER COLUMN 4 SET STATISTICS 1000;
ERROR: column number 4 of relation "attmp_idx" does not exist
ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS -1;
+ALTER TABLE attmp
+ ADD COLUMN col1 int GENERATED ALWAYS AS (a),
+ ADD COLUMN col2 int GENERATED ALWAYS AS (a + 1);
+CREATE INDEX attmp_idx1 ON attmp (a, col1, col2);
+ALTER INDEX attmp_idx1 ALTER COLUMN 1 SET STATISTICS 1000;
+ERROR: cannot alter statistics on non-expression column "a" of index "attmp_idx1"
+HINT: Alter statistics on table column instead.
+ALTER INDEX attmp_idx1 ALTER COLUMN 2 SET STATISTICS 1000;
+ERROR: cannot alter statistics on non-expression column "col1" of index "attmp_idx1"
+HINT: Alter statistics on table column instead.
+ALTER INDEX attmp_idx1 ALTER COLUMN 3 SET STATISTICS 1000;
+\d+ attmp_idx1
+ Index "public.attmp_idx1"
+ Column | Type | Key? | Definition | Storage | Stats target
+--------+---------+------+------------+---------+--------------
+ a | integer | yes | a | plain |
+ col1 | integer | yes | col1 | plain |
+ col2 | integer | yes | col2 | plain | 1000
+btree, for table "public.attmp"
+
DROP TABLE attmp;
--
-- rename - check on both non-temp and temp tables
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 1325e123877..c1ad91b23e5 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -2703,6 +2703,19 @@ SELECT * FROM t5 ORDER BY c ASC, a ASC;
3 | d1 | d1
(3 rows)
+CREATE INDEX t5_idx1 ON t5 USING btree((b COLLATE "POSIX"));
+CREATE INDEX t5_idx2 ON t5 USING btree((b));
+SELECT indexrelid::regclass, indrelid::regclass, indnatts, indattrgenerated,
+ indcollation[0]::regcollation
+FROM pg_index
+WHERE indrelid = 't5'::regclass
+ORDER BY indexrelid;
+ indexrelid | indrelid | indnatts | indattrgenerated | indcollation
+------------+----------+----------+------------------+--------------
+ t5_idx1 | t5 | 1 | 0 | "POSIX"
+ t5_idx2 | t5 | 1 | 0 | "C"
+(2 rows)
+
-- Check that DEFAULT expressions in SQL/JSON functions use the same collation
-- as the RETURNING type. Mismatched collations should raise an error.
CREATE DOMAIN d1 AS text COLLATE case_insensitive;
diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out
index ccbcdf8403f..ef19f667cc1 100644
--- a/src/test/regress/expected/fast_default.out
+++ b/src/test/regress/expected/fast_default.out
@@ -70,6 +70,14 @@ NOTICE: rewriting table has_volatile for reason 4
-- stored generated columns need a rewrite
ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored;
NOTICE: rewriting table has_volatile for reason 2
+-- if there is any index over virtual generated columns,
+-- change generation expression need rewrite
+CREATE INDEX on has_volatile(col6);
+ALTER TABLE has_volatile ALTER COLUMN col6 SET EXPRESSION AS (col1 * 3);
+NOTICE: rewriting table has_volatile for reason 2
+-- table rewrite again.
+ALTER TABLE has_volatile ALTER COLUMN col6 SET DATA TYPE INT8;
+NOTICE: rewriting table has_volatile for reason 4
-- Test a large sample of different datatypes
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
SELECT set('t');
diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out
index 249e68be654..147c6980540 100644
--- a/src/test/regress/expected/generated_virtual.out
+++ b/src/test/regress/expected/generated_virtual.out
@@ -745,30 +745,322 @@ ERROR: primary keys on virtual generated columns are not supported
--INSERT INTO gtest22b VALUES (2);
--INSERT INTO gtest22b VALUES (2);
-- indexes
-CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
---CREATE INDEX gtest22c_b_idx ON gtest22c (b);
+CREATE TABLE gtestparted (b integer, c integer, a integer GENERATED ALWAYS AS (c+1)) PARTITION BY RANGE (b);
+CREATE TABLE gtestpart3 (a integer GENERATED ALWAYS AS (c), b integer, c integer);
+ALTER TABLE gtestparted ATTACH PARTITION gtestpart3 FOR VALUES FROM (1) to (10);
+-- error: An index cannot be created when the partitioned table and its
+-- partitions have different generation expressions.
+CREATE INDEX gtestparted_a_idx_error on gtestparted(a);
+ERROR: cannot create index on partitioned table "gtestparted"
+DETAIL: The virtual generated column expression of partitioned table "gtestparted" does not match that of table "gtestpart3"
+ALTER TABLE gtestpart3 ALTER COLUMN a SET EXPRESSION AS (c + 1);
+CREATE TABLE gtestpart1 (c integer, a integer GENERATED ALWAYS AS (c), b integer);
+CREATE TABLE gtestpart2 (b integer, a integer GENERATED ALWAYS AS (c+1), c integer);
+CREATE INDEX gtestpart1_a_idx on gtestpart1(a);
+CREATE INDEX gtestpart2_a_idx on gtestpart2(a);
+CREATE INDEX gtestpart2_a_idx_copy on gtestpart2(a);
+CREATE INDEX gtestparted_a_idx on gtestparted(a);
+-- error: An index cannot be created when the partitioned table and its
+-- partitions have different generation expressions.
+ALTER TABLE gtestparted ATTACH PARTITION gtestpart1 FOR VALUES FROM (10) TO (20);
+ERROR: cannot attach table "gtestpart1" as partition of partitioned table "gtestparted"
+DETAIL: The virtual generated column expression of partitioned table "gtestparted" does not match that of table "gtestpart1"
+ALTER TABLE gtestparted ATTACH PARTITION gtestpart2 FOR VALUES FROM (10) TO (20); --ok
+SELECT * FROM pg_partition_tree('gtestparted_a_idx'::regclass);
+ relid | parentrelid | isleaf | level
+-------------------+-------------------+--------+-------
+ gtestparted_a_idx | | f | 0
+ gtestpart2_a_idx | gtestparted_a_idx | t | 1
+ gtestpart3_a_idx | gtestparted_a_idx | t | 1
+(3 rows)
+
+ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx_copy; --error
+ERROR: cannot attach index "gtestpart2_a_idx_copy" as a partition of index "gtestparted_a_idx"
+DETAIL: Another index "gtestpart2_a_idx" is already attached for partition "gtestpart2".
+ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx; --ok
+CREATE INDEX gtestparted_a_idx_1 on gtestparted(a);
+--now index gtestpart2_a_idx_copy should attach to the partition tree
+SELECT * FROM pg_partition_tree('gtestparted_a_idx_1'::regclass);
+ relid | parentrelid | isleaf | level
+-----------------------+---------------------+--------+-------
+ gtestparted_a_idx_1 | | f | 0
+ gtestpart2_a_idx_copy | gtestparted_a_idx_1 | t | 1
+ gtestpart3_a_idx1 | gtestparted_a_idx_1 | t | 1
+(3 rows)
+
+INSERT INTO gtestparted(b, c) SELECT g, g + 1 FROM generate_series(16, 1, -1) g;
+CLUSTER gtestparted USING gtestparted_a_idx_1;
+\d gtestpart2
+ Table "generated_virtual_tests.gtestpart2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+-----------------------------
+ b | integer | | |
+ a | integer | | | generated always as (c + 1)
+ c | integer | | |
+Partition of: gtestparted FOR VALUES FROM (10) TO (20)
+Indexes:
+ "gtestpart2_a_idx" btree (a)
+ "gtestpart2_a_idx_copy" btree (a) CLUSTER
+
+\d gtestpart3
+ Table "generated_virtual_tests.gtestpart3"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+-----------------------------
+ a | integer | | | generated always as (c + 1)
+ b | integer | | |
+ c | integer | | |
+Partition of: gtestparted FOR VALUES FROM (1) TO (10)
+Indexes:
+ "gtestpart3_a_idx" btree (a)
+ "gtestpart3_a_idx1" btree (a) CLUSTER
+
+--test create table like copy indexes
+CREATE TABLE gtestparted_like (LIKE gtestparted INCLUDING ALL);
+\d gtestparted_like
+ Table "generated_virtual_tests.gtestparted_like"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+-----------------------------
+ b | integer | | |
+ c | integer | | |
+ a | integer | | | generated always as (c + 1)
+Indexes:
+ "gtestparted_like_a_idx" btree (a)
+ "gtestparted_like_a_idx1" btree (a)
+
+ALTER TABLE gtestparted_like DROP COLUMN a;
+\d gtestparted_like
+ Table "generated_virtual_tests.gtestparted_like"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ b | integer | | |
+ c | integer | | |
+
+CREATE TABLE gtest22c (
+ a int,
+ b int GENERATED ALWAYS AS (a * 2),
+ c int GENERATED ALWAYS AS (11),
+ d int GENERATED ALWAYS AS (a * 3),
+ e int4range GENERATED ALWAYS AS (int4range(a, a + 10)),
+ e1 int8range GENERATED ALWAYS AS (int8range(a, a + 10)),
+ f int GENERATED ALWAYS AS (a),
+ f1 oid GENERATED ALWAYS AS (tableoid)
+) WITH (autovacuum_enabled = false);
+--index can not based on tableoid column
+CREATE INDEX gtest22c_error ON gtest22c (b, f1);
+ERROR: index creation on system columns is not supported
+LINE 1: CREATE INDEX gtest22c_error ON gtest22c (b, f1);
+ ^
+CREATE INDEX gtest22c_error ON gtest22c ((f1));
+ERROR: index creation on system columns is not supported
+LINE 1: CREATE INDEX gtest22c_error ON gtest22c ((f1));
+ ^
+ALTER TABLE gtest22c DROP COLUMN f1;
+--index include columns are not supported
+-- CREATE INDEX gtest22c_idx1_inc ON gtest22c USING btree(a) include (b,c);
+-- CREATE INDEX gtest22c_idx1_inc ON gtest22c USING btree(a) include (f);
+--Other index access methods are supported
+CREATE INDEX gtest22c_b_idx ON gtest22c USING btree(b, c);
+CREATE INDEX gtest22c_d_idx ON gtest22c USING hash(d);
+CREATE INDEX gtest22c_e_e1_idx ON gtest22c USING gist(e, e1);
+CREATE INDEX gtest22c_e1_idx ON gtest22c USING spgist(e1);
--CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
--CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
---\d gtest22c
---INSERT INTO gtest22c VALUES (1), (2), (3);
---SET enable_seqscan TO off;
---SET enable_bitmapscan TO off;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
---SELECT * FROM gtest22c WHERE b = 4;
+\d gtest22c
+ Table "generated_virtual_tests.gtest22c"
+ Column | Type | Collation | Nullable | Default
+--------+-----------+-----------+----------+--------------------------------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2)
+ c | integer | | | generated always as (11)
+ d | integer | | | generated always as (a * 3)
+ e | int4range | | | generated always as (int4range(a, a + 10))
+ e1 | int8range | | | generated always as (int8range(a::bigint, (a + 10)::bigint))
+ f | integer | | | generated always as (a)
+Indexes:
+ "gtest22c_b_idx" btree (b, c)
+ "gtest22c_d_idx" hash (d)
+ "gtest22c_e1_idx" spgist (e1)
+ "gtest22c_e_e1_idx" gist (e, e1)
+
+INSERT INTO gtest22c(a) SELECT g FROM generate_series(1, 1_000) g;
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
+ QUERY PLAN
+---------------------------------------------
+ Index Scan using gtest22c_b_idx on gtest22c
+ Index Cond: ((a * 2) = 4)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 4;
+ a | b | c | d | e | e1 | f
+---+---+----+---+--------+--------+---
+ 2 | 4 | 11 | 6 | [2,12) | [2,12) | 2
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4 AND c = 11;
+ QUERY PLAN
+---------------------------------------------
+ Index Scan using gtest22c_b_idx on gtest22c
+ Index Cond: ((a * 2) = 4)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 4 AND c = 11;
+ a | b | c | d | e | e1 | f
+---+---+----+---+--------+--------+---
+ 2 | 4 | 11 | 6 | [2,12) | [2,12) | 2
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE d = 6;
+ QUERY PLAN
+---------------------------------------------
+ Index Scan using gtest22c_d_idx on gtest22c
+ Index Cond: ((a * 3) = 6)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE d = 6;
+ a | b | c | d | e | e1 | f
+---+---+----+---+--------+--------+---
+ 2 | 4 | 11 | 6 | [2,12) | [2,12) | 2
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT count(*) FROM gtest22c WHERE e @> 12 AND e @> 16 AND e1 @> 12::bigint;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ -> Index Scan using gtest22c_e_e1_idx on gtest22c
+ Index Cond: ((int4range(a, (a + 10)) @> 12) AND (int4range(a, (a + 10)) @> 16) AND (int8range((a)::bigint, ((a + 10))::bigint) @> '12'::bigint))
+(3 rows)
+
+SELECT count(*) FROM gtest22c WHERE e @> 12 AND e @> 16 AND e1 @> 12::bigint;
+ count
+-------
+ 6
+(1 row)
+
+SELECT * FROM gtest22c WHERE e @> 12 AND e @> 16 AND e1 @> 12::bigint;
+ a | b | c | d | e | e1 | f
+----+----+----+----+---------+---------+----
+ 7 | 14 | 11 | 21 | [7,17) | [7,17) | 7
+ 8 | 16 | 11 | 24 | [8,18) | [8,18) | 8
+ 9 | 18 | 11 | 27 | [9,19) | [9,19) | 9
+ 10 | 20 | 11 | 30 | [10,20) | [10,20) | 10
+ 11 | 22 | 11 | 33 | [11,21) | [11,21) | 11
+ 12 | 24 | 11 | 36 | [12,22) | [12,22) | 12
+(6 rows)
+
+EXPLAIN (COSTS OFF) SELECT count(*) FROM gtest22c WHERE e1 @> 12::bigint AND e1 @> 16::bigint;
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+ -> Index Scan using gtest22c_e1_idx on gtest22c
+ Index Cond: ((int8range((a)::bigint, ((a + 10))::bigint) @> '12'::bigint) AND (int8range((a)::bigint, ((a + 10))::bigint) @> '16'::bigint))
+(3 rows)
+
+SELECT count(*) FROM gtest22c WHERE e1 @> 12::bigint AND e1 @> 16::bigint;
+ count
+-------
+ 6
+(1 row)
+
+SELECT * FROM gtest22c WHERE e1 @> 12::bigint AND e1 @> 16::bigint;
+ a | b | c | d | e | e1 | f
+----+----+----+----+---------+---------+----
+ 12 | 24 | 11 | 36 | [12,22) | [12,22) | 12
+ 11 | 22 | 11 | 33 | [11,21) | [11,21) | 11
+ 10 | 20 | 11 | 30 | [10,20) | [10,20) | 10
+ 9 | 18 | 11 | 27 | [9,19) | [9,19) | 9
+ 8 | 16 | 11 | 24 | [8,18) | [8,18) | 8
+ 7 | 14 | 11 | 21 | [7,17) | [7,17) | 7
+(6 rows)
+
+--column drop then the index over that column should also being dropped
+ALTER TABLE gtest22c DROP COLUMN e;
+CLUSTER gtest22c USING gtest22c_b_idx;
+\d gtest22c
+ Table "generated_virtual_tests.gtest22c"
+ Column | Type | Collation | Nullable | Default
+--------+-----------+-----------+----------+--------------------------------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2)
+ c | integer | | | generated always as (11)
+ d | integer | | | generated always as (a * 3)
+ e1 | int8range | | | generated always as (int8range(a::bigint, (a + 10)::bigint))
+ f | integer | | | generated always as (a)
+Indexes:
+ "gtest22c_b_idx" btree (b, c) CLUSTER
+ "gtest22c_d_idx" hash (d)
+ "gtest22c_e1_idx" spgist (e1)
+
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
--SELECT * FROM gtest22c WHERE b * 3 = 6;
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
--SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
---ANALYZE gtest22c;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
---SELECT * FROM gtest22c WHERE b = 8;
+ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
+ANALYZE gtest22c;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
+ QUERY PLAN
+---------------------------------------------
+ Index Scan using gtest22c_b_idx on gtest22c
+ Index Cond: ((a * 4) = 8)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 8;
+ a | b | c | d | e1 | f
+---+---+----+---+--------+---
+ 2 | 8 | 11 | 6 | [2,12) | 2
+(1 row)
+
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
--SELECT * FROM gtest22c WHERE b * 3 = 12;
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
--SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---RESET enable_seqscan;
---RESET enable_bitmapscan;
+--test index over gin and brin index
+RESET enable_bitmapscan;
+CREATE TABLE t2(
+ j jsonb,
+ j1 jsonb GENERATED ALWAYS AS (j || '{"hello": "world"}'),
+ f1 interval,
+ f2 interval GENERATED ALWAYS AS ( f1 + interval '1 day'));
+INSERT INTO t2(j, f1) SELECT i::text::jsonb, (i || ' days')::interval FROM generate_series(100, 240) s(i);
+INSERT INTO t2(f1) VALUES ('-infinity'), ('infinity');
+CREATE INDEX t2_f1_f2_brin_idx ON t2 USING BRIN (
+ f1 interval_minmax_multi_ops,
+ f2 interval_minmax_multi_ops) WITH (pages_per_range = 1);
+CREATE INDEX t2_j1_gin_idx ON t2 USING GIN (j1);
+EXPLAIN(COSTS OFF) SELECT count(*) FROM t2 WHERE j1 @> '[{"hello":"world"}]';
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on t2
+ Recheck Cond: ((j || '{"hello": "world"}'::jsonb) @> '[{"hello": "world"}]'::jsonb)
+ -> Bitmap Index Scan on t2_j1_gin_idx
+ Index Cond: ((j || '{"hello": "world"}'::jsonb) @> '[{"hello": "world"}]'::jsonb)
+(5 rows)
+
+SELECT count(*) FROM t2 WHERE j1 @> '[{"hello":"world"}]';
+ count
+-------
+ 141
+(1 row)
+
+EXPLAIN(COSTS OFF) SELECT * FROM t2 WHERE f1 > '30 years'::interval AND f2 > '30 years'::interval;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on t2
+ Recheck Cond: ((f1 > '@ 30 years'::interval) AND ((f1 + '@ 1 day'::interval) > '@ 30 years'::interval))
+ -> Bitmap Index Scan on t2_f1_f2_brin_idx
+ Index Cond: ((f1 > '@ 30 years'::interval) AND ((f1 + '@ 1 day'::interval) > '@ 30 years'::interval))
+(4 rows)
+
+SELECT * FROM t2 WHERE f1 > '30 years'::interval AND f2 > '30 years'::interval;
+ j | j1 | f1 | f2
+---+----+----------+----------
+ | | infinity | infinity
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
-- foreign keys
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
--INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33);
@@ -1313,6 +1605,7 @@ ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2');
DROP STATISTICS gtest31_2_stat;
CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b));
ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3');
+ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type
DROP TABLE gtest31_1, gtest31_2;
-- Check it for a partitioned table, too
CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text) PARTITION BY LIST (a);
@@ -1685,3 +1978,16 @@ select * from gtest33 where b is null;
reset constraint_exclusion;
drop table gtest33;
+-- system catalog sanity check. If the index is based on a virtual generated
+-- column, then the corresponding attribute's attgenerated should be 'v'.
+select pi.indrelid::regclass, pa.attnum,
+ pa.attname,
+ pa.attgenerated
+from pg_index pi, unnest(indattrgenerated) sub(a), pg_attribute pa
+where 0 <> a
+and pa.attrelid = pi.indrelid and pa.attnum = sub.a
+and (pa.attgenerated <> 'v' or pi.indnatts <> pi.indnkeyatts);
+ indrelid | attnum | attname | attgenerated
+----------+--------+---------+--------------
+(0 rows)
+
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
index 4d29fb85293..df4b700a798 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -702,6 +702,51 @@ select relname as child, inhparent::regclass as parent, pg_get_indexdef as child
idxpart_a_idx | | CREATE INDEX idxpart_a_idx ON ONLY public.idxpart USING btree (a COLLATE "C")
(7 rows)
+drop table idxpart;
+---test collation with virtual generated column
+drop table if exists idxpart, idxpart1, idxpart2;
+NOTICE: table "idxpart" does not exist, skipping
+NOTICE: table "idxpart1" does not exist, skipping
+NOTICE: table "idxpart2" does not exist, skipping
+create table idxpart (b text, a text generated always as (b), c text) partition by range (c);
+create table idxpart1 (b text, a text generated always as (b || 'h'), c text);
+create table idxpart2 (like idxpart including generated);
+create index idxpart2_a_idx on idxpart2 (a collate "POSIX");
+create index idxpart2_a_idx1 on idxpart2 (a);
+create index idxpart2_a_idx2 on idxpart2 (a collate "C");
+alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb');
+alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc');
+-- error: An index cannot be created when the partitioned table and its
+-- partitions have different generation expressions.
+create index idxpart_a on idxpart (a collate "C");
+ERROR: cannot create index on partitioned table "idxpart"
+DETAIL: The virtual generated column expression of partitioned table "idxpart" does not match that of table "idxpart1"
+alter table idxpart1 alter column a set expression as (b collate "C");
+create index idxpart_a on idxpart (a collate "C"); --error
+ERROR: cannot create index on partitioned table "idxpart"
+DETAIL: The virtual generated column expression of partitioned table "idxpart" does not match that of table "idxpart1"
+alter table idxpart1 alter column a set expression as (b);
+create index idxpart_a on idxpart (a collate "C"); ---ok
+create table idxpart4 partition of idxpart for values from ('ddd') to ('eee');
+select relname as child, inhparent::regclass as parent,
+ pi.indkey, pi.indattrgenerated,
+ (pi.indcollation[0])::regcollation,
+ pg_get_indexdef as childdef
+from pg_class join pg_index pi on pi.indexrelid = pg_class.oid
+left join pg_inherits on inhrelid = oid,
+lateral pg_get_indexdef(pg_class.oid)
+where relkind in ('i', 'I') and relname like 'idxpart%'
+order by relname;
+ child | parent | indkey | indattrgenerated | indcollation | childdef
+-----------------+-----------+--------+------------------+--------------+--------------------------------------------------------------------------------
+ idxpart1_a_idx | idxpart_a | 1 | 2 | "C" | CREATE INDEX idxpart1_a_idx ON public.idxpart1 USING btree (a COLLATE "C")
+ idxpart2_a_idx | | 1 | 2 | "POSIX" | CREATE INDEX idxpart2_a_idx ON public.idxpart2 USING btree (a COLLATE "POSIX")
+ idxpart2_a_idx1 | | 1 | 2 | "default" | CREATE INDEX idxpart2_a_idx1 ON public.idxpart2 USING btree (a)
+ idxpart2_a_idx2 | idxpart_a | 1 | 2 | "C" | CREATE INDEX idxpart2_a_idx2 ON public.idxpart2 USING btree (a COLLATE "C")
+ idxpart4_a_idx | idxpart_a | 1 | 2 | "C" | CREATE INDEX idxpart4_a_idx ON public.idxpart4 USING btree (a COLLATE "C")
+ idxpart_a | | 1 | 2 | "C" | CREATE INDEX idxpart_a ON ONLY public.idxpart USING btree (a COLLATE "C")
+(6 rows)
+
drop table idxpart;
-- Verify behavior for opclass (mis)matches
create table idxpart (a text) partition by range (a);
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 417202430a5..0b215d4daf6 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -156,6 +156,15 @@ ALTER INDEX attmp_idx ALTER COLUMN 4 SET STATISTICS 1000;
ALTER INDEX attmp_idx ALTER COLUMN 2 SET STATISTICS -1;
+ALTER TABLE attmp
+ ADD COLUMN col1 int GENERATED ALWAYS AS (a),
+ ADD COLUMN col2 int GENERATED ALWAYS AS (a + 1);
+
+CREATE INDEX attmp_idx1 ON attmp (a, col1, col2);
+ALTER INDEX attmp_idx1 ALTER COLUMN 1 SET STATISTICS 1000;
+ALTER INDEX attmp_idx1 ALTER COLUMN 2 SET STATISTICS 1000;
+ALTER INDEX attmp_idx1 ALTER COLUMN 3 SET STATISTICS 1000;
+\d+ attmp_idx1
DROP TABLE attmp;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index b6c54503d21..ab70aaed7e2 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -1000,6 +1000,14 @@ INSERT INTO t5 (a, b) values (1, 'D1'), (2, 'D2'), (3, 'd1');
-- rewriting.)
SELECT * FROM t5 ORDER BY c ASC, a ASC;
+CREATE INDEX t5_idx1 ON t5 USING btree((b COLLATE "POSIX"));
+CREATE INDEX t5_idx2 ON t5 USING btree((b));
+SELECT indexrelid::regclass, indrelid::regclass, indnatts, indattrgenerated,
+ indcollation[0]::regcollation
+FROM pg_index
+WHERE indrelid = 't5'::regclass
+ORDER BY indexrelid;
+
-- Check that DEFAULT expressions in SQL/JSON functions use the same collation
-- as the RETURNING type. Mismatched collations should raise an error.
CREATE DOMAIN d1 AS text COLLATE case_insensitive;
diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql
index 068dd0bc8aa..b39e76bcfc3 100644
--- a/src/test/regress/sql/fast_default.sql
+++ b/src/test/regress/sql/fast_default.sql
@@ -77,6 +77,12 @@ ALTER TABLE has_volatile ALTER COLUMN col1 SET DATA TYPE float8,
-- stored generated columns need a rewrite
ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored;
+-- if there is any index over virtual generated columns,
+-- change generation expression need rewrite
+CREATE INDEX on has_volatile(col6);
+ALTER TABLE has_volatile ALTER COLUMN col6 SET EXPRESSION AS (col1 * 3);
+-- table rewrite again.
+ALTER TABLE has_volatile ALTER COLUMN col6 SET DATA TYPE INT8;
-- Test a large sample of different datatypes
diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql
index 81152b39a79..c8e933c8300 100644
--- a/src/test/regress/sql/generated_virtual.sql
+++ b/src/test/regress/sql/generated_virtual.sql
@@ -1,6 +1,4 @@
-- keep these tests aligned with generated_stored.sql
-
-
CREATE SCHEMA generated_virtual_tests;
GRANT USAGE ON SCHEMA generated_virtual_tests TO PUBLIC;
SET search_path = generated_virtual_tests;
@@ -396,32 +394,138 @@ CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY
--INSERT INTO gtest22b VALUES (2);
-- indexes
-CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
---CREATE INDEX gtest22c_b_idx ON gtest22c (b);
+CREATE TABLE gtestparted (b integer, c integer, a integer GENERATED ALWAYS AS (c+1)) PARTITION BY RANGE (b);
+CREATE TABLE gtestpart3 (a integer GENERATED ALWAYS AS (c), b integer, c integer);
+
+ALTER TABLE gtestparted ATTACH PARTITION gtestpart3 FOR VALUES FROM (1) to (10);
+-- error: An index cannot be created when the partitioned table and its
+-- partitions have different generation expressions.
+CREATE INDEX gtestparted_a_idx_error on gtestparted(a);
+ALTER TABLE gtestpart3 ALTER COLUMN a SET EXPRESSION AS (c + 1);
+
+CREATE TABLE gtestpart1 (c integer, a integer GENERATED ALWAYS AS (c), b integer);
+CREATE TABLE gtestpart2 (b integer, a integer GENERATED ALWAYS AS (c+1), c integer);
+CREATE INDEX gtestpart1_a_idx on gtestpart1(a);
+CREATE INDEX gtestpart2_a_idx on gtestpart2(a);
+CREATE INDEX gtestpart2_a_idx_copy on gtestpart2(a);
+CREATE INDEX gtestparted_a_idx on gtestparted(a);
+
+-- error: An index cannot be created when the partitioned table and its
+-- partitions have different generation expressions.
+ALTER TABLE gtestparted ATTACH PARTITION gtestpart1 FOR VALUES FROM (10) TO (20);
+
+ALTER TABLE gtestparted ATTACH PARTITION gtestpart2 FOR VALUES FROM (10) TO (20); --ok
+SELECT * FROM pg_partition_tree('gtestparted_a_idx'::regclass);
+
+ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx_copy; --error
+ALTER INDEX gtestparted_a_idx ATTACH PARTITION gtestpart2_a_idx; --ok
+
+CREATE INDEX gtestparted_a_idx_1 on gtestparted(a);
+--now index gtestpart2_a_idx_copy should attach to the partition tree
+SELECT * FROM pg_partition_tree('gtestparted_a_idx_1'::regclass);
+
+INSERT INTO gtestparted(b, c) SELECT g, g + 1 FROM generate_series(16, 1, -1) g;
+CLUSTER gtestparted USING gtestparted_a_idx_1;
+\d gtestpart2
+\d gtestpart3
+
+--test create table like copy indexes
+CREATE TABLE gtestparted_like (LIKE gtestparted INCLUDING ALL);
+\d gtestparted_like
+ALTER TABLE gtestparted_like DROP COLUMN a;
+\d gtestparted_like
+
+CREATE TABLE gtest22c (
+ a int,
+ b int GENERATED ALWAYS AS (a * 2),
+ c int GENERATED ALWAYS AS (11),
+ d int GENERATED ALWAYS AS (a * 3),
+ e int4range GENERATED ALWAYS AS (int4range(a, a + 10)),
+ e1 int8range GENERATED ALWAYS AS (int8range(a, a + 10)),
+ f int GENERATED ALWAYS AS (a),
+ f1 oid GENERATED ALWAYS AS (tableoid)
+) WITH (autovacuum_enabled = false);
+
+--index can not based on tableoid column
+CREATE INDEX gtest22c_error ON gtest22c (b, f1);
+CREATE INDEX gtest22c_error ON gtest22c ((f1));
+ALTER TABLE gtest22c DROP COLUMN f1;
+
+--index include columns are not supported
+-- CREATE INDEX gtest22c_idx1_inc ON gtest22c USING btree(a) include (b,c);
+-- CREATE INDEX gtest22c_idx1_inc ON gtest22c USING btree(a) include (f);
+
+--Other index access methods are supported
+CREATE INDEX gtest22c_b_idx ON gtest22c USING btree(b, c);
+CREATE INDEX gtest22c_d_idx ON gtest22c USING hash(d);
+CREATE INDEX gtest22c_e_e1_idx ON gtest22c USING gist(e, e1);
+CREATE INDEX gtest22c_e1_idx ON gtest22c USING spgist(e1);
+
--CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
--CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
---\d gtest22c
+\d gtest22c
+
+INSERT INTO gtest22c(a) SELECT g FROM generate_series(1, 1_000) g;
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
+SELECT * FROM gtest22c WHERE b = 4;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4 AND c = 11;
+SELECT * FROM gtest22c WHERE b = 4 AND c = 11;
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE d = 6;
+SELECT * FROM gtest22c WHERE d = 6;
+
+EXPLAIN (COSTS OFF) SELECT count(*) FROM gtest22c WHERE e @> 12 AND e @> 16 AND e1 @> 12::bigint;
+SELECT count(*) FROM gtest22c WHERE e @> 12 AND e @> 16 AND e1 @> 12::bigint;
+SELECT * FROM gtest22c WHERE e @> 12 AND e @> 16 AND e1 @> 12::bigint;
+
+EXPLAIN (COSTS OFF) SELECT count(*) FROM gtest22c WHERE e1 @> 12::bigint AND e1 @> 16::bigint;
+SELECT count(*) FROM gtest22c WHERE e1 @> 12::bigint AND e1 @> 16::bigint;
+SELECT * FROM gtest22c WHERE e1 @> 12::bigint AND e1 @> 16::bigint;
+
+--column drop then the index over that column should also being dropped
+ALTER TABLE gtest22c DROP COLUMN e;
+CLUSTER gtest22c USING gtest22c_b_idx;
+\d gtest22c
---INSERT INTO gtest22c VALUES (1), (2), (3);
---SET enable_seqscan TO off;
---SET enable_bitmapscan TO off;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
---SELECT * FROM gtest22c WHERE b = 4;
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
--SELECT * FROM gtest22c WHERE b * 3 = 6;
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
--SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
---ANALYZE gtest22c;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
---SELECT * FROM gtest22c WHERE b = 8;
+ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
+ANALYZE gtest22c;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
+SELECT * FROM gtest22c WHERE b = 8;
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
--SELECT * FROM gtest22c WHERE b * 3 = 12;
--EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
--SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---RESET enable_seqscan;
---RESET enable_bitmapscan;
+
+--test index over gin and brin index
+RESET enable_bitmapscan;
+CREATE TABLE t2(
+ j jsonb,
+ j1 jsonb GENERATED ALWAYS AS (j || '{"hello": "world"}'),
+ f1 interval,
+ f2 interval GENERATED ALWAYS AS ( f1 + interval '1 day'));
+INSERT INTO t2(j, f1) SELECT i::text::jsonb, (i || ' days')::interval FROM generate_series(100, 240) s(i);
+INSERT INTO t2(f1) VALUES ('-infinity'), ('infinity');
+CREATE INDEX t2_f1_f2_brin_idx ON t2 USING BRIN (
+ f1 interval_minmax_multi_ops,
+ f2 interval_minmax_multi_ops) WITH (pages_per_range = 1);
+CREATE INDEX t2_j1_gin_idx ON t2 USING GIN (j1);
+
+EXPLAIN(COSTS OFF) SELECT count(*) FROM t2 WHERE j1 @> '[{"hello":"world"}]';
+SELECT count(*) FROM t2 WHERE j1 @> '[{"hello":"world"}]';
+
+EXPLAIN(COSTS OFF) SELECT * FROM t2 WHERE f1 > '30 years'::interval AND f2 > '30 years'::interval;
+SELECT * FROM t2 WHERE f1 > '30 years'::interval AND f2 > '30 years'::interval;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
-- foreign keys
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
@@ -891,3 +995,13 @@ select * from gtest33 where b is null;
reset constraint_exclusion;
drop table gtest33;
+
+-- system catalog sanity check. If the index is based on a virtual generated
+-- column, then the corresponding attribute's attgenerated should be 'v'.
+select pi.indrelid::regclass, pa.attnum,
+ pa.attname,
+ pa.attgenerated
+from pg_index pi, unnest(indattrgenerated) sub(a), pg_attribute pa
+where 0 <> a
+and pa.attrelid = pi.indrelid and pa.attnum = sub.a
+and (pa.attgenerated <> 'v' or pi.indnatts <> pi.indnkeyatts);
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
index b5cb01c2d70..192d67be1ec 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -328,6 +328,36 @@ select relname as child, inhparent::regclass as parent, pg_get_indexdef as child
where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
drop table idxpart;
+---test collation with virtual generated column
+drop table if exists idxpart, idxpart1, idxpart2;
+create table idxpart (b text, a text generated always as (b), c text) partition by range (c);
+create table idxpart1 (b text, a text generated always as (b || 'h'), c text);
+create table idxpart2 (like idxpart including generated);
+create index idxpart2_a_idx on idxpart2 (a collate "POSIX");
+create index idxpart2_a_idx1 on idxpart2 (a);
+create index idxpart2_a_idx2 on idxpart2 (a collate "C");
+alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb');
+alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc');
+-- error: An index cannot be created when the partitioned table and its
+-- partitions have different generation expressions.
+create index idxpart_a on idxpart (a collate "C");
+alter table idxpart1 alter column a set expression as (b collate "C");
+create index idxpart_a on idxpart (a collate "C"); --error
+alter table idxpart1 alter column a set expression as (b);
+create index idxpart_a on idxpart (a collate "C"); ---ok
+create table idxpart4 partition of idxpart for values from ('ddd') to ('eee');
+select relname as child, inhparent::regclass as parent,
+ pi.indkey, pi.indattrgenerated,
+ (pi.indcollation[0])::regcollation,
+ pg_get_indexdef as childdef
+from pg_class join pg_index pi on pi.indexrelid = pg_class.oid
+left join pg_inherits on inhrelid = oid,
+lateral pg_get_indexdef(pg_class.oid)
+where relkind in ('i', 'I') and relname like 'idxpart%'
+order by relname;
+
+drop table idxpart;
+
-- Verify behavior for opclass (mis)matches
create table idxpart (a text) partition by range (a);
create table idxpart1 (like idxpart);
--
2.34.1
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-02-27 10:45 Soumya S Murali <[email protected]>
parent: jian he <[email protected]>
1 sibling, 0 replies; 10+ messages in thread
From: Soumya S Murali @ 2026-02-27 10:45 UTC (permalink / raw)
To: jian he <[email protected]>; +Cc: Corey Huinker <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
Hi all,
On Thu, Feb 19, 2026 at 5:18 PM jian he <[email protected]> wrote:
>
> On Thu, Jul 31, 2025 at 2:24 AM Corey Huinker <[email protected]> wrote:
> >
> >>
> >> Besides, this does nothing you haven't been able to do for
> >> decades with expression indexes.
> >
> >
> > What I'm hoping for with this feature is a smoother transition when people start introducing virtual columns. If there was already an index on that expression, and some queries start using the new virtual column (with the same expression), will that cause those queries to miss the index we already have? If it doesn't, then a customer can roll out the query changes at will and not need to do some cut-over from using the expression to using the virtual column.
> >
>
> hi.
> I am not sure whether this concern has been addressed, as I am still somewhat
> confused by the above paragraph.
>
> As noted earlier, creating an index on a virtual generated column results in a
> new index, and that index behaves the same as a regular expression index.
>
> An updated and polished patch is attached. The regress tests are quite verbose
> at the moment, since I make it covered all index types (btree, gist, spgist,
> hash, gin, and brin).
>
I went through the discussions and have reviewed the patch.
During testing, I encountered a segmentation fault during initdb which
occurred due to a crash in bootstrap mode when ComputeIndexAttrs() was
invoked with pstate == NULL while system catalog indexes were being
created. The virtual generated column handling logic was executing
unconditionally, which is unsafe during bootstrap because catalog and
syscache state are not fully initialized. I fixed this by guarding the
virtual generated column logic to skip the logic when
IsBootstrapProcessingMode() is true so that it can prevent the
generated-column rewrite from executing during bootstrap. After the
fix, initdb no longer crashes in bootstrap mode, the full regression
test suite passes cleanly, the server starts normally and key
behaviors like index creation on virtual generated columns, planner
rewrite, dependency tracking, partitioned table support, and
dump/restore roundtrips are all correct. Also I performed some
additional validations to ensure consistent behaviours. With the
bootstrap guard in place, the patch seems functionally correct,
catalog-safe, and dump/restore safe.
Kindly review the patch. Looking forward to more feedback.
Regards,
Soumya
Attachments:
[text/x-patch] 0001-skip-virtual-generated-column-handling-during-bootst.patch (1.0K, ../../CAMtXxw8_nBrTs2X9UudPnNjhVkJuJ8mQvVP8jqnWCyC3_KbZYA@mail.gmail.com/2-0001-skip-virtual-generated-column-handling-during-bootst.patch)
download | inline diff:
From 1d8378ed05e35c303a1ad8120a59bca8a1157554 Mon Sep 17 00:00:00 2001
From: Soumya <[email protected]>
Date: Fri, 27 Feb 2026 16:00:05 +0530
Subject: [PATCH] skip virtual generated column handling during bootstrap
Signed-off-by: Soumya <[email protected]>
---
src/backend/commands/indexcmds.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 7579afffcc9..7875e178aea 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1991,7 +1991,9 @@ ComputeIndexAttrs(ParseState *pstate,
* indexes. We therefore collect the virtual generated columns attribute
* number for subsequent verification of expression attributes.
*/
- if (reltupldesc->constr && reltupldesc->constr->has_generated_virtual)
+ if (!IsBootstrapProcessingMode() &&
+ reltupldesc->constr &&
+ reltupldesc->constr->has_generated_virtual)
{
for (int i = 0; i < reltupldesc->natts; i++)
{
--
2.34.1
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-07-05 03:04 =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
parent: jian he <[email protected]>
1 sibling, 2 replies; 10+ messages in thread
From: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= @ 2026-07-05 03:04 UTC (permalink / raw)
To: =?utf-8?B?amlhbiBoZQ==?= <[email protected]>; =?utf-8?B?UGV0ZXIgRWlzZW50cmF1dA==?= <[email protected]>; =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]>; +Cc: =?utf-8?B?Q29yZXkgSHVpbmtlcg==?= <[email protected]>; =?utf-8?B?VG9tIExhbmU=?= <[email protected]>; =?utf-8?B?Y2hlbmdwZW5nX3lhbg==?= <[email protected]>; =?utf-8?B?Z3VvZmVuZ2xpbnV4?= <[email protected]>; =?utf-8?B?5oiR6Ieq5bex55qE6YKu566x?= <[email protected]>
>On Fri, Mar 13, 2026 at 10:01?PM Peter Eisentraut <[email protected]> wrote:
>>
>> I think you could do a much simpler initial version of this if you just
>> supported virtual generated columns in expression indexes. And then
>> prohibit SET EXPRESSION if the column is used in an index. Then you
>> don't need to worry about index rebuilding, ALTER TABLE recursion, new
>> catalog columns, and all that.
>>
>
>CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a) VIRTUAL);
>CREATE INDEX gtest22c_a_idx ON gtest22c (a);
>CREATE INDEX gtest22c_b_idx ON gtest22c (b);
>
>If we don't add a new catalog column (just a single boolean
>indisvirtual is not enough, i think),
>how can we distinguish between the gtest22c_a_idx and gtest22c_b_idx
>indexes in the example above?
>
>If CREATE INDEX simply expands the virtual generated column expression
>without dependency tracking, that would be quite easy, see the
>attached v8.
>If so, we need to explicitly document that SET EXPRESSION has no
>effect on existing indexes that originally referenced the virtual
>generated column when CREATE INDEX was used.
>
>> But there is a comment in DefineIndex():
>>
>> /*
>> * XXX Virtual generated columns in index expressions or predicates
>> * could be supported, but it needs support in
>> * RelationGetIndexExpressions() and RelationGetIndexPredicate().
>> */
>>
>> which you delete, but you don't make any changes to those mentioned
>> functions. Maybe the comment is wrong, in which case, let's discuss
>> that and fix it. (If the comment is indeed wrong, then the feature
>> might even be very easy.)
>>
>
>I don't think it's a good idea to store the virtual generated columns
>as is in Form_pg_index->indkey
>because IndexInfo->ii_IndexAttrNumbers and Form->pg_index->indkey are
>referenced in too many places (see BuildIndexInfo).
>For every single occurrence of ndkey.values[i], we need to consider
>whether it's ok for it be a virtual generated column.
>Instead, Anum_pg_index_indexprs and Anum_pg_index_indpred store the
>expressions after the virtual generated columns expansion,
>then we don't need to worry about Form_pg_index->indkey.values[i] is
>virtual generated column or not.
>
>Therefore, i think RelationGetIndexExpressions and
>RelationGetIndexPredicate don't need to
>deal with virtual generated column expressions at all.
>Overall, I think the comment above is wrong.
>
>
>
>--
>jian
>https://www.enterprisedb.com/
Hi, hackers
Quick glossary:
VGC = virtual generated columns
SGC = stored generated columns
### 1. Iteration of implementation attempts
I first drafted V0: no VGC expansion during index creation, only temporary expansion at runtime.
The scope of required changes kept expanding (see `v0.patch.txt`), so I abandoned this approach.
Next I tried V1, expanding VGCs directly on index creation to support VGC in index expressions,
`INCLUDE` columns and partial index `WHERE` clauses. Unfortunately this would break metadata
catalog immutability. Before finalizing the patch (see `v1.patch.txt`), I found prior community
discussions and prototype attempts with several major design revisions:
https://postgr.es/m/CACJufxGao-cypdNhifHAdt8jHfK6-HX=tRBovBkgRuxw063GaA@mail.gmail.com
https://postgr.es/m/CACJufxGgkH0PyyqP6ggqcEWHxZzmkV=puY8ad=s8kisss9MAwg@mail.gmail.com
https://postgr.es/m/[email protected]
https://www.postgresql.org/message-id/flat/CACJufxExe7%2BGh5MKMFiN5xwwmPXaaZC1jjQOFuovv8Q7b1LgFw%40m...
https://www.postgresql.org/message-id/flat/CACJufxGgkH0PyyqP6ggqcEWHxZzmkV%3DpuY8ad%3Ds8kisss9MAwg%4...
https://commitfest.postgresql.org/patch/5667
https://commitfest.postgresql.org/patch/6094/
https://www.postgresql.org/message-id/flat/CAMbWs4811nG3w_3sLxps%2BEAuUsffA_83ZQ-1acEH_jQeq117mg%40m...
I then redesigned for V2. Developing this version made it clear how complex the feature is. Every change
was reviewed for local behavior plus upstream/downstream impacts, though some edge cases may still be overlooked.
### 2. Core design principles
1. **Goal**: Enable VGC support for regular index columns, expression columns, `INCLUDE` columns and columns used
in partial index predicates.
2. **Immutable metadata catalog**: We must not alter existing catalog entries(see v2-SQL_test_result.xlsx). VGCs are
benchmarked against SGCs to guarantee no regressions for index rebuild, logical replication, index comparison, stats
collection and `pg_dump`.
3. **Runtime expansion required**: The parser expands VGCs during query analysis, while SGCs are stored unexpanded
at index creation. Without runtime expansion, the planner cannot properly select access paths, sort strategies or partial indexes.
I added paired expand fields and helper functions to `IndexInfo`, `RelationData` and `IndexOptInfo`. Expanded expressions
are built once instead of dynamically on each access, hence paired code paths for raw and expanded fields.
4. I updated comments for `ComputeIndexAttrs()` and `makeIndexInfo()`. These functions remain unmodified; we expand
the new fields right after calling them for later access safety.
5. Major changes to `FormIndexDatum`: VGC values are computed on the fly via expanded expressions and stored inindex
tuples, behaving like SGCs to support index key evaluation, INCLUDE columns, expression indexes, and partial index matching.
### 3. Main challenge
The most tricky part is distinguishing which code paths should use raw non-expanded nodes versus expanded ones,
which consumed most of my development effort.
### 4. Code review scope
I audited all code paths for the following functions/fields to choose expansion mode and assess side effects:
makeIndexInfo|indpredExpand|indpred|indexprsExpand|indexprs|ii_PredicateState|ii_PredicateExpandState|
ii_PredicateExpand|ii_Predicate|ii_ExpressionsState|ii_ExpressionsExpandState|ii_ExpressionsExpand|ii_Expressions|
RelationGetIndexPredicateExpand |RelationGetIndexPredicate|RelationGetIndexExpressionsExpand|
RelationGetIndexExpressions|FormIndexDatum|ComputeIndexAttrs|rd_indpredExpand|rd_indpred|rd_indexprsExpand|rd_indexprs.
All changes and behavioral impacts are recorded in
v2-相关成员、函数的整体分析.xlsx (Chinese)
v2-Comprehensive Analysis of Related Members and Functions.xlsx (English).
### 5. Current status
While I have made substantial changes, full test coverage may still be incomplete. Community review, testing and
feedback are highly appreciated. Based on current progress and my testing (which is admittedly not comprehensive
or thorough), it aligns with SGCs in most metadata catalog aspects. see "v2-SQL_test_result.xlsx" including checking SQL.
Basic index scans now work, but there are still minor planner inconsistencies that I am debugging and resolving.
xman5=# \d+ t1
Table "public.t1"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+------------------------------------+---------+-------------+--------------+-------------
a | integer | | | | plain | | |
s1 | integer | | | generated always as (a * 1) stored | plain | | |
s2 | integer | | | generated always as (a * 2) stored | plain | | |
s3 | integer | | | generated always as (a * 3) stored | plain | | |
s4 | integer | | | generated always as (a * 4) stored | plain | | |
v1 | integer | | | generated always as (a * 11) | plain | | |
v2 | integer | | | generated always as (a * 22) | plain | | |
v3 | integer | | | generated always as (a * 33) | plain | | |
v4 | integer | | | generated always as (a * 44) | plain | | |
Indexes:
"idx_t1_s" btree (a, s1, abs(s2)) INCLUDE (s3) WHERE s4 < 100
"idx_t1_v" btree (a, v1, abs(v2)) INCLUDE (v3) WHERE v4 < 100
Access method: heap
Forexample:
xman5=# explain select a from t1 where s4 < 100;
QUERY PLAN
------------------------------------------------------------------------
Index Only Scan using idx_t1_s on t1 (cost=0.13..8.14 rows=1 width=4)
(1 row)
xman5=# explain select a from t1 where v4 < 100;
QUERY PLAN
-------------------------------------------------------------------------
Bitmap Heap Scan on t1 (cost=4.18..10.48 rows=153 width=4) -------still need heap Scan and Recheck Cond
Recheck Cond: ((a * 44) < 100)
-> Bitmap Index Scan on idx_t1_v (cost=0.00..4.14 rows=153 width=0)
(3 rows)
regards,
--
ZizhuanLiu (X-MAN)
[email protected]
Attachments:
[application/octet-stream] v0.patch.txt (12.5K, ../../[email protected]/2-v0.patch.txt)
download
[application/octet-stream] v1.patch.txt (6.9K, ../../[email protected]/3-v1.patch.txt)
download
[application/octet-stream] v2-相关成员、函数的整体分析.xlsx (47.4K, ../../[email protected]/4-v2-%E7%9B%B8%E5%85%B3%E6%88%90%E5%91%98%E3%80%81%E5%87%BD%E6%95%B0%E7%9A%84%E6%95%B4%E4%BD%93%E5%88%86%E6%9E%90.xlsx)
download
[application/octet-stream] v2-Comprehensive Analysis of Related Members and Functions.xlsx (47.3K, ../../[email protected]/5-v2-Comprehensive%20Analysis%20of%20Related%20Members%20and%20Functions.xlsx)
download
[application/octet-stream] v2-SQL_test_result.xlsx (27.7K, ../../[email protected]/6-v2-SQL_test_result.xlsx)
download
[application/octet-stream] v2.pathch.txt (34.1K, ../../[email protected]/7-v2.pathch.txt)
download
[application/octet-stream] v2.source_files.tar (1.8M, ../../[email protected]/8-v2.source_files.tar)
download
[application/octet-stream] v2-0001-v2-enhance-index-support-for-virtual-generated-co.patch (35.3K, ../../[email protected]/9-v2-0001-v2-enhance-index-support-for-virtual-generated-co.patch)
download | inline diff:
From 1098404a9b7f963b8e1c685f8166b76dd4c6cc82 Mon Sep 17 00:00:00 2001
From: Zizhuan Liu <[email protected]>
Date: Sun, 5 Jul 2026 10:30:49 +0800
Subject: [PATCH v2] v2 enhance-index-support-for-virtual-generated-columns
---
src/backend/access/heap/heapam_handler.c | 20 ++--
src/backend/bootstrap/bootstrap.c | 6 ++
src/backend/catalog/index.c | 61 +++++++++--
src/backend/catalog/indexing.c | 2 +
src/backend/catalog/toasting.c | 4 +
src/backend/commands/analyze.c | 10 +-
src/backend/commands/indexcmds.c | 42 +++-----
src/backend/executor/execIndexing.c | 13 ++-
src/backend/nodes/makefuncs.c | 10 +-
src/backend/optimizer/path/indxpath.c | 18 ++--
src/backend/optimizer/plan/createplan.c | 6 +-
src/backend/optimizer/util/plancat.c | 22 +++-
src/backend/utils/adt/selfuncs.c | 4 +-
src/backend/utils/cache/relcache.c | 126 +++++++++++++++++++++++
src/include/nodes/execnodes.h | 4 +
src/include/nodes/pathnodes.h | 2 +
src/include/utils/rel.h | 2 +
src/include/utils/relcache.h | 4 +
18 files changed, 286 insertions(+), 70 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2268cc27..22e0762e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1159,7 +1159,7 @@ heapam_index_build_range_scan(Relation heapRelation,
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
double reltuples;
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1200,7 +1200,7 @@ heapam_index_build_range_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. In a normal index build, we use
@@ -1607,9 +1607,9 @@ heapam_index_build_range_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1709,7 +1709,9 @@ heapam_index_build_range_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
return reltuples;
}
@@ -1726,7 +1728,7 @@ heapam_index_validate_scan(Relation heapRelation,
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1758,7 +1760,7 @@ heapam_index_validate_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. We need just those tuples
@@ -1895,9 +1897,9 @@ heapam_index_validate_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1952,7 +1954,9 @@ heapam_index_validate_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b0dcd987..80bf4fc4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1156,11 +1156,17 @@ index_register(Oid heap,
/* expressions will likely be null, but may as well copy it */
newind->il_info->ii_Expressions =
copyObject(indexInfo->ii_Expressions);
+ newind->il_info->ii_ExpressionsExpand =
+ copyObject(indexInfo->ii_ExpressionsExpand);
newind->il_info->ii_ExpressionsState = NIL;
+ newind->il_info->ii_ExpressionsExpandState = NIL;
/* predicate will likely be null, but may as well copy it */
newind->il_info->ii_Predicate =
copyObject(indexInfo->ii_Predicate);
+ newind->il_info->ii_PredicateExpand =
+ copyObject(indexInfo->ii_PredicateExpand);
newind->il_info->ii_PredicateState = NULL;
+ newind->il_info->ii_PredicateExpandState = NULL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357..ee260fde 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1403,6 +1403,10 @@ index_create_copy(Relation heapRelation, uint16 flags,
concurrently, /* concurrent */
indexRelation->rd_indam->amsummarizing,
oldInfo->ii_WithoutOverlaps);
+ newInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid);
+ newInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid);
/* fetch exclusion constraint info if any */
if (indexRelation->rd_index->indisexclusion)
@@ -2471,6 +2475,8 @@ BuildIndexInfo(Relation index)
false,
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
+ ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index);
+ ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
@@ -2760,10 +2766,12 @@ FormIndexDatum(IndexInfo *indexInfo,
/* First time through, set up expression evaluation state */
indexInfo->ii_ExpressionsState =
ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+ indexInfo->ii_ExpressionsExpandState =
+ ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate);
/* Check caller has set up context correctly */
Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
}
- indexpr_item = list_head(indexInfo->ii_ExpressionsState);
+ indexpr_item = list_head(indexInfo->ii_ExpressionsExpandState);
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
@@ -2775,11 +2783,40 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
- /*
- * Plain index column; get the value we need directly from the
- * heap tuple.
- */
- iDatum = slot_getattr(slot, keycol, &isNull);
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute att = TupleDescAttr(tupdesc, keycol - 1);
+
+ if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ /*
+ * Plain index column; get the value we need directly from the
+ * heap tuple.
+ */
+ iDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ TupleConstr *constr = tupdesc->constr;
+
+ for (int j = 0; j < constr->num_defval; j++)
+ {
+ AttrDefault *defval;
+ Expr *expr = NULL;
+ ExprState *exprstate;
+
+ defval = &constr->defval[j];
+ if (defval->adnum == keycol)
+ {
+ expr = stringToNode(defval->adbin);
+ exprstate = ExecPrepareExpr(expr, estate);
+ iDatum = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull);
+ break;
+ }
+ Assert(j + 1 < constr->num_defval);
+ }
+ }
}
else
{
@@ -2791,7 +2828,7 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item),
GetPerTupleExprContext(estate),
&isNull);
- indexpr_item = lnext(indexInfo->ii_ExpressionsState, indexpr_item);
+ indexpr_item = lnext(indexInfo->ii_ExpressionsExpandState, indexpr_item);
}
values[i] = iDatum;
isnull[i] = isNull;
@@ -3220,7 +3257,7 @@ IndexCheckExclusion(Relation heapRelation,
TableScanDesc scan;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -3246,7 +3283,7 @@ IndexCheckExclusion(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Scan all live tuples in the base relation.
@@ -3266,9 +3303,9 @@ IndexCheckExclusion(Relation heapRelation,
/*
* In a partial index, ignore tuples that don't satisfy the predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -3301,7 +3338,9 @@ IndexCheckExclusion(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fd7d2ec0..d27aaa09 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -133,7 +133,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
+ Assert(indexInfo->ii_ExpressionsExpand == NIL);
Assert(indexInfo->ii_Predicate == NIL);
+ Assert(indexInfo->ii_PredicateExpand == NIL);
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(index->rd_index->indimmediate);
Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 4aa52a4b..daebfda1 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -298,9 +298,13 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_IndexAttrNumbers[0] = 1;
indexInfo->ii_IndexAttrNumbers[1] = 2;
indexInfo->ii_Expressions = NIL;
+ indexInfo->ii_ExpressionsExpand = NIL;
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_Predicate = NIL;
+ indexInfo->ii_PredicateExpand = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b7..fc67c24a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -899,7 +899,7 @@ compute_index_stats(Relation onerel, double totalrows,
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
- ExprState *predicate;
+ ExprState *predicateExpand;
Datum *exprvals;
bool *exprnulls;
int numindexrows,
@@ -908,7 +908,7 @@ compute_index_stats(Relation onerel, double totalrows,
double totalindexrows;
/* Ignore index if no columns to analyze and not partial */
- if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
+ if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL)
continue;
/*
@@ -926,7 +926,7 @@ compute_index_stats(Relation onerel, double totalrows,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/* Compute and save index expression values */
exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
@@ -949,9 +949,9 @@ compute_index_stats(Relation onerel, double totalrows,
ExecStoreHeapTuple(heapTuple, slot, false);
/* If index is partial, check predicate */
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
numindexrows++;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8d..8a63efc8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -932,6 +932,8 @@ DefineIndex(ParseState *pstate,
concurrent,
amissummarizing,
stmt->iswithoutoverlaps);
+ indexInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
@@ -948,6 +950,8 @@ DefineIndex(ParseState *pstate,
root_save_userid, root_save_sec_context,
&root_save_nestlevel);
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_ExpressionsExpand, rel, InvalidOid);
/*
* Extra checks when creating a PRIMARY KEY index.
*/
@@ -1110,7 +1114,7 @@ DefineIndex(ParseState *pstate,
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
*
- * Also disallow virtual generated columns in indexes (use expression
+ * Aisallow virtual generated columns in indexes (include using expression
* index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1123,7 +1127,7 @@ DefineIndex(ParseState *pstate,
errmsg("index creation on system columns is not supported")));
- if (attno > 0 &&
+ /*if (attno > 0 && i < numberOfKeyAttributes &&
TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1131,17 +1135,15 @@ DefineIndex(ParseState *pstate,
errmsg("primary keys on virtual generated columns are not supported") :
stmt->isconstraint ?
errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported"));
+ errmsg("indexes on virtual generated columns are not supported"));*/
}
/*
- * Also check for system and generated columns used in expressions or
- * predicates.
+ * Also check for system columns used in expressions or predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
- int j;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1154,25 +1156,6 @@ DefineIndex(ParseState *pstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
-
- /*
- * XXX Virtual generated columns in index expressions or predicates
- * could be supported, but it needs support in
- * RelationGetIndexExpressions() and RelationGetIndexPredicate().
- */
- j = -1;
- while ((j = bms_next_member(indexattrs, j)) >= 0)
- {
- AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
-
- if (attno > 0 &&
- TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- stmt->isconstraint ?
- errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported")));
- }
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
@@ -1876,6 +1859,11 @@ CheckPredicate(Expr *predicate)
* If the caller switched to the table owner, ddl_userid is the role for ACL
* checks reached without traversing opaque expressions. Otherwise, it's
* InvalidOid, and other ddl_* arguments are undefined.
+ *
+ * Upon returning from this function, callers must apply
+ * ExpandVirtualGeneratedColumns() to ii_ExpressionsExpand
+ * when necessary for actual expansion if ii_ExpressionsExpand
+ * is not NIL or dummy.
*/
static void
ComputeIndexAttrs(ParseState *pstate,
@@ -2274,6 +2262,10 @@ ComputeIndexAttrs(ParseState *pstate,
attn++;
}
+
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions),
+ NULL, relId);
}
/*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index eb383812..b47daaf5 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -380,20 +380,24 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -616,20 +620,24 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1102,6 +1110,7 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
* pass hint.
*/
idxExprs = RelationGetIndexExpressions(indexRelation);
+ idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation));
hasexpression = index_expression_changed_walker((Node *) idxExprs,
allUpdatedCols);
list_free(idxExprs);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40b09958..946891b9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -828,7 +828,11 @@ make_ands_implicit(Expr *clause)
/*
* makeIndexInfo
- * create an IndexInfo node
+ * create an IndexInfo node. Upon returning from this function,
+ * callers must apply ExpandVirtualGeneratedColumns()
+ * or RelationGetIndexExpressionsExpand or RelationGetIndexPredicateExpand() to
+ * ii_ExpressionsExpand and ii_PredicateExpand as needed for actual
+ * expansion when they are not NIL or dummy.
*/
IndexInfo *
makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
@@ -856,11 +860,15 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
/* expressions */
n->ii_Expressions = expressions;
+ n->ii_ExpressionsExpand = copyObject(expressions);
n->ii_ExpressionsState = NIL;
+ n->ii_ExpressionsExpandState = NIL;
/* predicates */
n->ii_Predicate = predicates;
+ n->ii_PredicateExpand = copyObject(predicates);
n->ii_PredicateState = NULL;
+ n->ii_PredicateExpandState = NULL;
/* exclusion constraints */
n->ii_ExclusionOps = NULL;
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3..b716ee92 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1119,7 +1119,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
* just scanning the predOK index alone, no OR.)
*/
useful_predicate = false;
- if (index->indpred != NIL)
+ if (index->indpredExpand != NIL)
{
if (index->predOK)
{
@@ -1131,10 +1131,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
if (all_clauses == NIL)
all_clauses = list_concat_copy(clauses, other_clauses);
- if (!predicate_implied_by(index->indpred, all_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, all_clauses, false))
continue; /* can't use it at all */
- if (!predicate_implied_by(index->indpred, other_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, other_clauses, false))
useful_predicate = true;
}
}
@@ -2183,7 +2183,7 @@ find_indexpath_quals(Path *bitmapqual, List **quals, List **preds)
*quals = lappend(*quals, iclause->rinfo->clause);
}
- *preds = list_concat(*preds, ipath->indexinfo->indpred);
+ *preds = list_concat(*preds, ipath->indexinfo->indpredExpand);
}
else
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
@@ -4038,11 +4038,11 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
ListCell *lcr;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
continue; /* ignore non-partial indexes here */
if (!index->predOK) /* don't repeat work if already proven OK */
- index->predOK = predicate_implied_by(index->indpred, clauselist,
+ index->predOK = predicate_implied_by(index->indpredExpand, clauselist,
false);
/* If rel is an update target, leave indrestrictinfo as set above */
@@ -4068,7 +4068,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
/* predicate_implied_by() assumes first arg is immutable */
if (contain_mutable_functions((Node *) rinfo->clause) ||
!predicate_implied_by(list_make1(rinfo->clause),
- index->indpred, false))
+ index->indpredExpand, false))
index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
}
}
@@ -4402,14 +4402,14 @@ match_index_to_operand(Node *operand,
int i;
Node *indexkey;
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (i = 0; i < indexcol; i++)
{
if (index->indexkeys[i] == 0)
{
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
if (indexpr_item == NULL)
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183d..2bdad7bd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3340,7 +3340,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subindexECs = lappend(subindexECs, rinfo->parent_ec);
}
/* We can add any index predicate conditions, too */
- foreach(l, ipath->indexinfo->indpred)
+ foreach(l, ipath->indexinfo->indpredExpand)
{
Expr *pred = (Expr *) lfirst(l);
@@ -5142,7 +5142,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
}
/* It's an index expression, so find and cross-check the expression */
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
@@ -5167,7 +5167,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
else
elog(ERROR, "index key does not match expected index column");
}
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7c4be174..72846d9e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -433,24 +433,38 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* properly reduced.
*/
info->indexprs = RelationGetIndexExpressions(indexRelation);
+ info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation);
info->indpred = RelationGetIndexPredicate(indexRelation);
+ info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation);
if (info->indexprs)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0);
+ }
info->indexprs = (List *)
eval_const_expressions(root, (Node *) info->indexprs);
+ info->indexprsExpand = (List *)
+ eval_const_expressions(root, (Node *) info->indexprsExpand);
}
if (info->indpred)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0);
+ }
info->indpred = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(info->indpred));
+ info->indpredExpand = (List *)
+ eval_const_expressions(root,
+ (Node *) make_ands_explicit(info->indpredExpand));
info->indpred = make_ands_implicit((Expr *) info->indpred);
+ info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand);
}
/* Build targetlist using the completed indexprs data */
@@ -941,8 +955,8 @@ infer_arbiter_indexes(PlannerInfo *root)
attno - FirstLowInvalidHeapAttributeNumber);
}
- inferElems = RelationGetIndexExpressions(idxRel);
- inferIndexExprs = RelationGetIndexPredicate(idxRel);
+ inferElems = RelationGetIndexExpressionsExpand(idxRel);
+ inferIndexExprs = RelationGetIndexPredicateExpand(idxRel);
break;
}
}
@@ -1072,7 +1086,7 @@ infer_arbiter_indexes(PlannerInfo *root)
continue;
/* Expression attributes (if any) must match */
- idxExprs = RelationGetIndexExpressions(idxRel);
+ idxExprs = RelationGetIndexExpressionsExpand(idxRel);
if (idxExprs)
{
if (varno != 1)
@@ -1139,7 +1153,7 @@ infer_arbiter_indexes(PlannerInfo *root)
if (list_difference(idxExprs, inferElems) != NIL)
continue;
- predExprs = RelationGetIndexPredicate(idxRel);
+ predExprs = RelationGetIndexPredicateExpand(idxRel);
if (predExprs)
{
if (varno != 1)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d6efd070..98e7216e 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -7639,10 +7639,10 @@ add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
List *predExtraQuals = NIL;
ListCell *lc;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
return indexQuals;
- foreach(lc, index->indpred)
+ foreach(lc, index->indpredExpand)
{
Node *predQual = (Node *) lfirst(lc);
List *oneQual = list_make1(predQual);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0572ab42..e604bb91 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -91,6 +91,7 @@
#include "utils/resowner.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */
@@ -1583,7 +1584,9 @@ RelationInitIndexAccessInfo(Relation relation)
* expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
+ relation->rd_indexprsExpand = NIL;
relation->rd_indpred = NIL;
+ relation->rd_indpredExpand = NIL;
relation->rd_exclops = NULL;
relation->rd_exclprocs = NULL;
relation->rd_exclstrats = NULL;
@@ -5146,6 +5149,45 @@ RelationGetIndexExpressions(Relation relation)
return result;
}
+List *
+RelationGetIndexExpressionsExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indexprsExpand)
+ return copyObject(relation->rd_indexprsExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
+ return NIL;
+
+ result = RelationGetIndexExpressions(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indexprsExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetDummyIndexExpressions -- get dummy expressions for an index
*
@@ -5266,6 +5308,45 @@ RelationGetIndexPredicate(Relation relation)
return result;
}
+List *
+RelationGetIndexPredicateExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indpredExpand)
+ return copyObject(relation->rd_indpredExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
+ return NIL;
+
+ result = RelationGetIndexPredicate(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indpredExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
*
@@ -6490,7 +6571,9 @@ load_relcache_init_file(bool shared)
rel->rd_partcheckvalid = false;
rel->rd_partcheckcxt = NULL;
rel->rd_indexprs = NIL;
+ rel->rd_indexprsExpand = NIL;
rel->rd_indpred = NIL;
+ rel->rd_indpredExpand = NIL;
rel->rd_exclops = NULL;
rel->rd_exclprocs = NULL;
rel->rd_exclstrats = NULL;
@@ -7019,3 +7102,46 @@ ResOwnerReleaseRelation(Datum res)
RelationCloseCleanup((Relation) DatumGetPointer(res));
}
+
+List *
+ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId)
+{
+ bool opened_relation = false;
+ TupleDesc tupdesc;
+
+ if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid))
+ return list;
+
+ if (heapRelation == NULL)
+ {
+ heapRelation = table_open(heapRelId, NoLock);
+ opened_relation = true;
+ }
+
+ tupdesc = RelationGetDescr(heapRelation);
+ if ((tupdesc->constr && tupdesc->constr->has_generated_virtual))
+ {
+ int j;
+ Bitmapset *indexattrs = NULL;
+
+ pull_varattnos((Node *)list, 1, &indexattrs);
+
+ j = -1;
+ while ((j = bms_next_member(indexattrs, j)) >= 0)
+ {
+ AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno > 0 &&
+ TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1);
+ break;
+ }
+ }
+ }
+
+ if (opened_relation)
+ table_close(heapRelation, NoLock);
+
+ return list;
+}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 53c13831..20a2e618 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -190,13 +190,17 @@ typedef struct IndexInfo
/* expr trees for expression entries, or NIL if none */
List *ii_Expressions; /* list of Expr */
+ List *ii_ExpressionsExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
List *ii_ExpressionsState; /* list of ExprState */
+ List *ii_ExpressionsExpandState; /* list of ExprState */
/* partial-index predicate, or NIL if none */
List *ii_Predicate; /* list of Expr */
+ List *ii_PredicateExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
ExprState *ii_PredicateState;
+ ExprState *ii_PredicateExpandState;
/* Per-column exclusion operators, or NULL if none */
Oid *ii_ExclusionOps; /* array with one entry per column */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c681..9c65e123 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,8 +1403,10 @@ typedef struct IndexOptInfo
* print indextlist
*/
List *indexprs pg_node_attr(read_write_ignore);
+ List *indexprsExpand pg_node_attr(read_write_ignore);
/* predicate if a partial index, else NIL */
List *indpred;
+ List *indpredExpand;
/* targetlist representing index columns */
List *indextlist;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa07ebf8..7bd01b36 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -210,7 +210,9 @@ typedef struct RelationData
struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
+ List *rd_indexprsExpand; /* expanded index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
+ List *rd_indpredExpand; /* expanded index predicate tree, if any */
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 89c27aa1..70531bb8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -58,8 +58,10 @@ extern List *RelationGetStatExtList(Relation relation);
extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
+extern List *RelationGetIndexExpressionsExpand(Relation relation);
extern List *RelationGetDummyIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetIndexPredicateExpand(Relation relation);
extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
/*
@@ -161,4 +163,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt;
/* should be used only by relcache.c and postinit.c */
extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
+extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId);
+
#endif /* RELCACHE_H */
--
2.43.0
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-07-05 09:00 =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
parent: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
1 sibling, 0 replies; 10+ messages in thread
From: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= @ 2026-07-05 09:00 UTC (permalink / raw)
To: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>; =?utf-8?B?amlhbiBoZQ==?= <[email protected]>; =?utf-8?B?UGV0ZXIgRWlzZW50cmF1dA==?= <[email protected]>; =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]>; +Cc: =?utf-8?B?Q29yZXkgSHVpbmtlcg==?= <[email protected]>; =?utf-8?B?VG9tIExhbmU=?= <[email protected]>; =?utf-8?B?Y2hlbmdwZW5nX3lhbg==?= <[email protected]>; =?utf-8?B?Z3VvZmVuZ2xpbnV4?= <[email protected]>; =?utf-8?B?5oiR6Ieq5bex55qE6YKu566x?= <[email protected]>
I've checked the CFbot test results; failures are mostly
limitedto `generated_virtual.sql` and its related test cases.
regards,
--
ZizhuanLiu (X-MAN)
[email protected]
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-07-06 16:09 =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
parent: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
1 sibling, 1 reply; 10+ messages in thread
From: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= @ 2026-07-06 16:09 UTC (permalink / raw)
To: =?utf-8?B?amlhbiBoZQ==?= <[email protected]>; =?utf-8?B?UGV0ZXIgRWlzZW50cmF1dA==?= <[email protected]>; =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]>; +Cc: =?utf-8?B?Q29yZXkgSHVpbmtlcg==?= <[email protected]>; =?utf-8?B?VG9tIExhbmU=?= <[email protected]>; =?utf-8?B?Y2hlbmdwZW5nX3lhbg==?= <[email protected]>; =?utf-8?B?Z3VvZmVuZ2xpbnV4?= <[email protected]>; =?utf-8?B?5oiR6Ieq5bex55qE6YKu566x?= <[email protected]>
>Original
>From: ZizhuanLiu X-MAN <[email protected]>
>Date: 2026-07-05 11:04
>To: jian he <[email protected]>, Peter Eisentraut <[email protected]>, pgsql-hackers <[email protected]>
>Cc: Corey Huinker <[email protected]>, Tom Lane <[email protected]>, chengpeng_yan <[email protected]>, guofenglinux <[email protected]>, 我自己的邮箱 <[email protected]>
>Subject: Re: support create index on virtual generated column.
>
>Basic index scans now work, but there are still minor planner inconsistencies that I am debugging and resolving.
>xman5=# \d+ t1
> Table "public.t1"
> Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
>--------+---------+-----------+----------+------------------------------------+---------+-------------+--------------+-------------
> a | integer | | | | plain | | |
> s1 | integer | | | generated always as (a * 1) stored | plain | | |
> s2 | integer | | | generated always as (a * 2) stored | plain | | |
> s3 | integer | | | generated always as (a * 3) stored | plain | | |
> s4 | integer | | | generated always as (a * 4) stored | plain | | |
> v1 | integer | | | generated always as (a * 11) | plain | | |
> v2 | integer | | | generated always as (a * 22) | plain | | |
> v3 | integer | | | generated always as (a * 33) | plain | | |
> v4 | integer | | | generated always as (a * 44) | plain | | |
>Indexes:
> "idx_t1_s" btree (a, s1, abs(s2)) INCLUDE (s3) WHERE s4 < 100
> "idx_t1_v" btree (a, v1, abs(v2)) INCLUDE (v3) WHERE v4 < 100
>Access method: heap
>
>
>
>Forexample:
>xman5=# explain select a from t1 where s4 < 100;
> QUERY PLAN
>------------------------------------------------------------------------
> Index Only Scan using idx_t1_s on t1 (cost=0.13..8.14 rows=1 width=4)
>(1 row)
>
>
>
>xman5=# explain select a from t1 where v4 < 100;
> QUERY PLAN
>-------------------------------------------------------------------------
> Bitmap Heap Scan on t1 (cost=4.18..10.48 rows=153 width=4) -------still need heap Scan and Recheck Cond
> Recheck Cond: ((a * 44) < 100)
> -> Bitmap Index Scan on idx_t1_v (cost=0.00..4.14 rows=153 width=0)
>(3 rows)
Hi,
I built further optimizations based on the V2 patch and supplemented logic
in the planner to align the execution plans of idx_t1_v and idx_t1_s.
In cost_index(), I am confident about setting path->path.rows = index->tuples
when index->predOK == true. However, I’m not entirely sure whether we should
also assign index->tuples to baserel->rows.
Besides, I left the branch guarded by if (path->path.param_info) untouched.
This approach likely has flaws, and I’m unsure how to handle it properly.
I welcome all feedback, discussions and corrections.
Attached are the execution plans for idx_t1_v and idx_t1_s:
```SQL
xman5=# explain analyze select a from t1 where s4 < 100;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on t1 (cost=4.14..7.92 rows=2 width=4) (actual time=0.074..0.076 rows=2.00 loops=1)
Recheck Cond: (s4 < 100)
Heap Blocks: exact=1
Buffers: shared hit=2
-> Bitmap Index Scan on idx_t1_s (cost=0.00..4.14 rows=2 width=0) (actual time=0.019..0.019 rows=2.00 loops=1)
Index Searches: 1
Buffers: shared hit=1
Planning Time: 0.420 ms
Execution Time: 0.118 ms
(9 rows)
xman5=# explain analyze select a from t1 where v4 < 100;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on t1 (cost=4.14..7.93 rows=2 width=4) (actual time=0.061..0.063 rows=2.00 loops=1)
Recheck Cond: ((a * 44) < 100)
Heap Blocks: exact=1
Buffers: shared hit=2
-> Bitmap Index Scan on idx_t1_v (cost=0.00..4.14 rows=2 width=0) (actual time=0.019..0.020 rows=2.00 loops=1)
Index Searches: 1
Buffers: shared hit=1
Planning Time: 0.364 ms
Execution Time: 0.126 ms
(9 rows)
```SQL
regards,
--
ZizhuanLiu (X-MAN)
[email protected]
Attachments:
[application/octet-stream] v3-0001-v2-enhance-index-support-for-virtual-generated-co.patch (37.4K, ../../[email protected]/2-v3-0001-v2-enhance-index-support-for-virtual-generated-co.patch)
download | inline diff:
From 8dcc051e432499a6082200d8b92870e8e41a297d Mon Sep 17 00:00:00 2001
From: Zizhuan Liu <[email protected]>
Date: Mon, 6 Jul 2026 23:23:26 +0800
Subject: [PATCH v3] v2 enhance-index-support-for-virtual-generated-columns
---
src/backend/access/heap/heapam_handler.c | 20 ++--
src/backend/bootstrap/bootstrap.c | 6 ++
src/backend/catalog/index.c | 61 +++++++++--
src/backend/catalog/indexing.c | 2 +
src/backend/catalog/toasting.c | 4 +
src/backend/commands/analyze.c | 10 +-
src/backend/commands/indexcmds.c | 42 +++-----
src/backend/executor/execIndexing.c | 13 ++-
src/backend/nodes/makefuncs.c | 10 +-
src/backend/optimizer/path/costsize.c | 9 +-
src/backend/optimizer/path/indxpath.c | 18 ++--
src/backend/optimizer/plan/createplan.c | 6 +-
src/backend/optimizer/util/plancat.c | 22 +++-
src/backend/utils/adt/selfuncs.c | 21 +++-
src/backend/utils/cache/relcache.c | 126 +++++++++++++++++++++++
src/include/nodes/execnodes.h | 4 +
src/include/nodes/pathnodes.h | 2 +
src/include/utils/rel.h | 2 +
src/include/utils/relcache.h | 4 +
19 files changed, 311 insertions(+), 71 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2268cc27..22e0762e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1159,7 +1159,7 @@ heapam_index_build_range_scan(Relation heapRelation,
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
double reltuples;
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1200,7 +1200,7 @@ heapam_index_build_range_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. In a normal index build, we use
@@ -1607,9 +1607,9 @@ heapam_index_build_range_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1709,7 +1709,9 @@ heapam_index_build_range_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
return reltuples;
}
@@ -1726,7 +1728,7 @@ heapam_index_validate_scan(Relation heapRelation,
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1758,7 +1760,7 @@ heapam_index_validate_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. We need just those tuples
@@ -1895,9 +1897,9 @@ heapam_index_validate_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1952,7 +1954,9 @@ heapam_index_validate_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b0dcd987..80bf4fc4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1156,11 +1156,17 @@ index_register(Oid heap,
/* expressions will likely be null, but may as well copy it */
newind->il_info->ii_Expressions =
copyObject(indexInfo->ii_Expressions);
+ newind->il_info->ii_ExpressionsExpand =
+ copyObject(indexInfo->ii_ExpressionsExpand);
newind->il_info->ii_ExpressionsState = NIL;
+ newind->il_info->ii_ExpressionsExpandState = NIL;
/* predicate will likely be null, but may as well copy it */
newind->il_info->ii_Predicate =
copyObject(indexInfo->ii_Predicate);
+ newind->il_info->ii_PredicateExpand =
+ copyObject(indexInfo->ii_PredicateExpand);
newind->il_info->ii_PredicateState = NULL;
+ newind->il_info->ii_PredicateExpandState = NULL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357..ee260fde 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1403,6 +1403,10 @@ index_create_copy(Relation heapRelation, uint16 flags,
concurrently, /* concurrent */
indexRelation->rd_indam->amsummarizing,
oldInfo->ii_WithoutOverlaps);
+ newInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid);
+ newInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid);
/* fetch exclusion constraint info if any */
if (indexRelation->rd_index->indisexclusion)
@@ -2471,6 +2475,8 @@ BuildIndexInfo(Relation index)
false,
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
+ ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index);
+ ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
@@ -2760,10 +2766,12 @@ FormIndexDatum(IndexInfo *indexInfo,
/* First time through, set up expression evaluation state */
indexInfo->ii_ExpressionsState =
ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+ indexInfo->ii_ExpressionsExpandState =
+ ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate);
/* Check caller has set up context correctly */
Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
}
- indexpr_item = list_head(indexInfo->ii_ExpressionsState);
+ indexpr_item = list_head(indexInfo->ii_ExpressionsExpandState);
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
@@ -2775,11 +2783,40 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
- /*
- * Plain index column; get the value we need directly from the
- * heap tuple.
- */
- iDatum = slot_getattr(slot, keycol, &isNull);
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute att = TupleDescAttr(tupdesc, keycol - 1);
+
+ if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ /*
+ * Plain index column; get the value we need directly from the
+ * heap tuple.
+ */
+ iDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ TupleConstr *constr = tupdesc->constr;
+
+ for (int j = 0; j < constr->num_defval; j++)
+ {
+ AttrDefault *defval;
+ Expr *expr = NULL;
+ ExprState *exprstate;
+
+ defval = &constr->defval[j];
+ if (defval->adnum == keycol)
+ {
+ expr = stringToNode(defval->adbin);
+ exprstate = ExecPrepareExpr(expr, estate);
+ iDatum = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull);
+ break;
+ }
+ Assert(j + 1 < constr->num_defval);
+ }
+ }
}
else
{
@@ -2791,7 +2828,7 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item),
GetPerTupleExprContext(estate),
&isNull);
- indexpr_item = lnext(indexInfo->ii_ExpressionsState, indexpr_item);
+ indexpr_item = lnext(indexInfo->ii_ExpressionsExpandState, indexpr_item);
}
values[i] = iDatum;
isnull[i] = isNull;
@@ -3220,7 +3257,7 @@ IndexCheckExclusion(Relation heapRelation,
TableScanDesc scan;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -3246,7 +3283,7 @@ IndexCheckExclusion(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Scan all live tuples in the base relation.
@@ -3266,9 +3303,9 @@ IndexCheckExclusion(Relation heapRelation,
/*
* In a partial index, ignore tuples that don't satisfy the predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -3301,7 +3338,9 @@ IndexCheckExclusion(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fd7d2ec0..d27aaa09 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -133,7 +133,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
+ Assert(indexInfo->ii_ExpressionsExpand == NIL);
Assert(indexInfo->ii_Predicate == NIL);
+ Assert(indexInfo->ii_PredicateExpand == NIL);
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(index->rd_index->indimmediate);
Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 4aa52a4b..daebfda1 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -298,9 +298,13 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_IndexAttrNumbers[0] = 1;
indexInfo->ii_IndexAttrNumbers[1] = 2;
indexInfo->ii_Expressions = NIL;
+ indexInfo->ii_ExpressionsExpand = NIL;
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_Predicate = NIL;
+ indexInfo->ii_PredicateExpand = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b7..fc67c24a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -899,7 +899,7 @@ compute_index_stats(Relation onerel, double totalrows,
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
- ExprState *predicate;
+ ExprState *predicateExpand;
Datum *exprvals;
bool *exprnulls;
int numindexrows,
@@ -908,7 +908,7 @@ compute_index_stats(Relation onerel, double totalrows,
double totalindexrows;
/* Ignore index if no columns to analyze and not partial */
- if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
+ if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL)
continue;
/*
@@ -926,7 +926,7 @@ compute_index_stats(Relation onerel, double totalrows,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/* Compute and save index expression values */
exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
@@ -949,9 +949,9 @@ compute_index_stats(Relation onerel, double totalrows,
ExecStoreHeapTuple(heapTuple, slot, false);
/* If index is partial, check predicate */
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
numindexrows++;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8d..8a63efc8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -932,6 +932,8 @@ DefineIndex(ParseState *pstate,
concurrent,
amissummarizing,
stmt->iswithoutoverlaps);
+ indexInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
@@ -948,6 +950,8 @@ DefineIndex(ParseState *pstate,
root_save_userid, root_save_sec_context,
&root_save_nestlevel);
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_ExpressionsExpand, rel, InvalidOid);
/*
* Extra checks when creating a PRIMARY KEY index.
*/
@@ -1110,7 +1114,7 @@ DefineIndex(ParseState *pstate,
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
*
- * Also disallow virtual generated columns in indexes (use expression
+ * Aisallow virtual generated columns in indexes (include using expression
* index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1123,7 +1127,7 @@ DefineIndex(ParseState *pstate,
errmsg("index creation on system columns is not supported")));
- if (attno > 0 &&
+ /*if (attno > 0 && i < numberOfKeyAttributes &&
TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1131,17 +1135,15 @@ DefineIndex(ParseState *pstate,
errmsg("primary keys on virtual generated columns are not supported") :
stmt->isconstraint ?
errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported"));
+ errmsg("indexes on virtual generated columns are not supported"));*/
}
/*
- * Also check for system and generated columns used in expressions or
- * predicates.
+ * Also check for system columns used in expressions or predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
- int j;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1154,25 +1156,6 @@ DefineIndex(ParseState *pstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
-
- /*
- * XXX Virtual generated columns in index expressions or predicates
- * could be supported, but it needs support in
- * RelationGetIndexExpressions() and RelationGetIndexPredicate().
- */
- j = -1;
- while ((j = bms_next_member(indexattrs, j)) >= 0)
- {
- AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
-
- if (attno > 0 &&
- TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- stmt->isconstraint ?
- errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported")));
- }
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
@@ -1876,6 +1859,11 @@ CheckPredicate(Expr *predicate)
* If the caller switched to the table owner, ddl_userid is the role for ACL
* checks reached without traversing opaque expressions. Otherwise, it's
* InvalidOid, and other ddl_* arguments are undefined.
+ *
+ * Upon returning from this function, callers must apply
+ * ExpandVirtualGeneratedColumns() to ii_ExpressionsExpand
+ * when necessary for actual expansion if ii_ExpressionsExpand
+ * is not NIL or dummy.
*/
static void
ComputeIndexAttrs(ParseState *pstate,
@@ -2274,6 +2262,10 @@ ComputeIndexAttrs(ParseState *pstate,
attn++;
}
+
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions),
+ NULL, relId);
}
/*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index eb383812..b47daaf5 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -380,20 +380,24 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -616,20 +620,24 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1102,6 +1110,7 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
* pass hint.
*/
idxExprs = RelationGetIndexExpressions(indexRelation);
+ idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation));
hasexpression = index_expression_changed_walker((Node *) idxExprs,
allUpdatedCols);
list_free(idxExprs);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40b09958..946891b9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -828,7 +828,11 @@ make_ands_implicit(Expr *clause)
/*
* makeIndexInfo
- * create an IndexInfo node
+ * create an IndexInfo node. Upon returning from this function,
+ * callers must apply ExpandVirtualGeneratedColumns()
+ * or RelationGetIndexExpressionsExpand or RelationGetIndexPredicateExpand() to
+ * ii_ExpressionsExpand and ii_PredicateExpand as needed for actual
+ * expansion when they are not NIL or dummy.
*/
IndexInfo *
makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
@@ -856,11 +860,15 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
/* expressions */
n->ii_Expressions = expressions;
+ n->ii_ExpressionsExpand = copyObject(expressions);
n->ii_ExpressionsState = NIL;
+ n->ii_ExpressionsExpandState = NIL;
/* predicates */
n->ii_Predicate = predicates;
+ n->ii_PredicateExpand = copyObject(predicates);
n->ii_PredicateState = NULL;
+ n->ii_PredicateExpandState = NULL;
/* exclusion constraints */
n->ii_ExclusionOps = NULL;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56..34fc147a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -594,7 +594,14 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
}
else
{
- path->path.rows = baserel->rows;
+ /*
+ * If index->predOK holds true, neither the baserel's estimated output tuples
+ * nor this index path's estimated output tuples can exceed the index's total tuples.
+ */
+ if (!index->predOK)
+ path->path.rows = baserel->rows;
+ else
+ path->path.rows = baserel->rows = index->tuples;
/* qpquals come from just the rel's restriction clauses */
qpquals = extract_nonindex_conditions(path->indexinfo->indrestrictinfo,
path->indexclauses);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3..b716ee92 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1119,7 +1119,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
* just scanning the predOK index alone, no OR.)
*/
useful_predicate = false;
- if (index->indpred != NIL)
+ if (index->indpredExpand != NIL)
{
if (index->predOK)
{
@@ -1131,10 +1131,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
if (all_clauses == NIL)
all_clauses = list_concat_copy(clauses, other_clauses);
- if (!predicate_implied_by(index->indpred, all_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, all_clauses, false))
continue; /* can't use it at all */
- if (!predicate_implied_by(index->indpred, other_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, other_clauses, false))
useful_predicate = true;
}
}
@@ -2183,7 +2183,7 @@ find_indexpath_quals(Path *bitmapqual, List **quals, List **preds)
*quals = lappend(*quals, iclause->rinfo->clause);
}
- *preds = list_concat(*preds, ipath->indexinfo->indpred);
+ *preds = list_concat(*preds, ipath->indexinfo->indpredExpand);
}
else
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
@@ -4038,11 +4038,11 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
ListCell *lcr;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
continue; /* ignore non-partial indexes here */
if (!index->predOK) /* don't repeat work if already proven OK */
- index->predOK = predicate_implied_by(index->indpred, clauselist,
+ index->predOK = predicate_implied_by(index->indpredExpand, clauselist,
false);
/* If rel is an update target, leave indrestrictinfo as set above */
@@ -4068,7 +4068,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
/* predicate_implied_by() assumes first arg is immutable */
if (contain_mutable_functions((Node *) rinfo->clause) ||
!predicate_implied_by(list_make1(rinfo->clause),
- index->indpred, false))
+ index->indpredExpand, false))
index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
}
}
@@ -4402,14 +4402,14 @@ match_index_to_operand(Node *operand,
int i;
Node *indexkey;
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (i = 0; i < indexcol; i++)
{
if (index->indexkeys[i] == 0)
{
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
if (indexpr_item == NULL)
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183d..2bdad7bd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3340,7 +3340,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subindexECs = lappend(subindexECs, rinfo->parent_ec);
}
/* We can add any index predicate conditions, too */
- foreach(l, ipath->indexinfo->indpred)
+ foreach(l, ipath->indexinfo->indpredExpand)
{
Expr *pred = (Expr *) lfirst(l);
@@ -5142,7 +5142,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
}
/* It's an index expression, so find and cross-check the expression */
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
@@ -5167,7 +5167,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
else
elog(ERROR, "index key does not match expected index column");
}
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7c4be174..72846d9e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -433,24 +433,38 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* properly reduced.
*/
info->indexprs = RelationGetIndexExpressions(indexRelation);
+ info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation);
info->indpred = RelationGetIndexPredicate(indexRelation);
+ info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation);
if (info->indexprs)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0);
+ }
info->indexprs = (List *)
eval_const_expressions(root, (Node *) info->indexprs);
+ info->indexprsExpand = (List *)
+ eval_const_expressions(root, (Node *) info->indexprsExpand);
}
if (info->indpred)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0);
+ }
info->indpred = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(info->indpred));
+ info->indpredExpand = (List *)
+ eval_const_expressions(root,
+ (Node *) make_ands_explicit(info->indpredExpand));
info->indpred = make_ands_implicit((Expr *) info->indpred);
+ info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand);
}
/* Build targetlist using the completed indexprs data */
@@ -941,8 +955,8 @@ infer_arbiter_indexes(PlannerInfo *root)
attno - FirstLowInvalidHeapAttributeNumber);
}
- inferElems = RelationGetIndexExpressions(idxRel);
- inferIndexExprs = RelationGetIndexPredicate(idxRel);
+ inferElems = RelationGetIndexExpressionsExpand(idxRel);
+ inferIndexExprs = RelationGetIndexPredicateExpand(idxRel);
break;
}
}
@@ -1072,7 +1086,7 @@ infer_arbiter_indexes(PlannerInfo *root)
continue;
/* Expression attributes (if any) must match */
- idxExprs = RelationGetIndexExpressions(idxRel);
+ idxExprs = RelationGetIndexExpressionsExpand(idxRel);
if (idxExprs)
{
if (varno != 1)
@@ -1139,7 +1153,7 @@ infer_arbiter_indexes(PlannerInfo *root)
if (list_difference(idxExprs, inferElems) != NIL)
continue;
- predExprs = RelationGetIndexPredicate(idxRel);
+ predExprs = RelationGetIndexPredicateExpand(idxRel);
if (predExprs)
{
if (varno != 1)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d6efd070..94e21719 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -7431,6 +7431,7 @@ genericcostestimate(PlannerInfo *root,
double qual_arg_cost;
List *selectivityQuals;
ListCell *l;
+ bool needCalcIndexSelectivity = true;
/*
* If the index is partial, AND the index predicate with the explicitly
@@ -7495,9 +7496,19 @@ genericcostestimate(PlannerInfo *root,
* indexSelectivity estimate is tiny.
*/
if (numIndexTuples > index->tuples)
+ {
numIndexTuples = index->tuples;
+ needCalcIndexSelectivity = true;
+ }
+
+ /* Recalculate indexSelectivity based on the estimated numIndexTuples */
if (numIndexTuples < 1.0)
+ {
numIndexTuples = 1.0;
+ indexSelectivity = 0.0;
+ }
+ else if (needCalcIndexSelectivity && index->rel->tuples > 0)
+ indexSelectivity = numIndexTuples / index->rel->tuples;
/*
* Estimate the number of index pages that will be retrieved.
@@ -7639,10 +7650,10 @@ add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
List *predExtraQuals = NIL;
ListCell *lc;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
return indexQuals;
- foreach(lc, index->indpred)
+ foreach(lc, index->indpredExpand)
{
Node *predQual = (Node *) lfirst(lc);
List *oneQual = list_make1(predQual);
@@ -8054,6 +8065,12 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
JOIN_INNER,
NULL);
numIndexTuples = btreeSelectivity * index->rel->tuples;
+ /*
+ * The number of leaf tuples visited for cost
+ * cannot exceed the total tuples of the index.
+ */
+ if (numIndexTuples > index->tuples)
+ numIndexTuples = index->tuples;
/*
* btree automatically combines individual array element primitive
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0572ab42..e604bb91 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -91,6 +91,7 @@
#include "utils/resowner.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */
@@ -1583,7 +1584,9 @@ RelationInitIndexAccessInfo(Relation relation)
* expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
+ relation->rd_indexprsExpand = NIL;
relation->rd_indpred = NIL;
+ relation->rd_indpredExpand = NIL;
relation->rd_exclops = NULL;
relation->rd_exclprocs = NULL;
relation->rd_exclstrats = NULL;
@@ -5146,6 +5149,45 @@ RelationGetIndexExpressions(Relation relation)
return result;
}
+List *
+RelationGetIndexExpressionsExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indexprsExpand)
+ return copyObject(relation->rd_indexprsExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
+ return NIL;
+
+ result = RelationGetIndexExpressions(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indexprsExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetDummyIndexExpressions -- get dummy expressions for an index
*
@@ -5266,6 +5308,45 @@ RelationGetIndexPredicate(Relation relation)
return result;
}
+List *
+RelationGetIndexPredicateExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indpredExpand)
+ return copyObject(relation->rd_indpredExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
+ return NIL;
+
+ result = RelationGetIndexPredicate(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indpredExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
*
@@ -6490,7 +6571,9 @@ load_relcache_init_file(bool shared)
rel->rd_partcheckvalid = false;
rel->rd_partcheckcxt = NULL;
rel->rd_indexprs = NIL;
+ rel->rd_indexprsExpand = NIL;
rel->rd_indpred = NIL;
+ rel->rd_indpredExpand = NIL;
rel->rd_exclops = NULL;
rel->rd_exclprocs = NULL;
rel->rd_exclstrats = NULL;
@@ -7019,3 +7102,46 @@ ResOwnerReleaseRelation(Datum res)
RelationCloseCleanup((Relation) DatumGetPointer(res));
}
+
+List *
+ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId)
+{
+ bool opened_relation = false;
+ TupleDesc tupdesc;
+
+ if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid))
+ return list;
+
+ if (heapRelation == NULL)
+ {
+ heapRelation = table_open(heapRelId, NoLock);
+ opened_relation = true;
+ }
+
+ tupdesc = RelationGetDescr(heapRelation);
+ if ((tupdesc->constr && tupdesc->constr->has_generated_virtual))
+ {
+ int j;
+ Bitmapset *indexattrs = NULL;
+
+ pull_varattnos((Node *)list, 1, &indexattrs);
+
+ j = -1;
+ while ((j = bms_next_member(indexattrs, j)) >= 0)
+ {
+ AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno > 0 &&
+ TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1);
+ break;
+ }
+ }
+ }
+
+ if (opened_relation)
+ table_close(heapRelation, NoLock);
+
+ return list;
+}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 53c13831..20a2e618 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -190,13 +190,17 @@ typedef struct IndexInfo
/* expr trees for expression entries, or NIL if none */
List *ii_Expressions; /* list of Expr */
+ List *ii_ExpressionsExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
List *ii_ExpressionsState; /* list of ExprState */
+ List *ii_ExpressionsExpandState; /* list of ExprState */
/* partial-index predicate, or NIL if none */
List *ii_Predicate; /* list of Expr */
+ List *ii_PredicateExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
ExprState *ii_PredicateState;
+ ExprState *ii_PredicateExpandState;
/* Per-column exclusion operators, or NULL if none */
Oid *ii_ExclusionOps; /* array with one entry per column */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c681..9c65e123 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,8 +1403,10 @@ typedef struct IndexOptInfo
* print indextlist
*/
List *indexprs pg_node_attr(read_write_ignore);
+ List *indexprsExpand pg_node_attr(read_write_ignore);
/* predicate if a partial index, else NIL */
List *indpred;
+ List *indpredExpand;
/* targetlist representing index columns */
List *indextlist;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa07ebf8..7bd01b36 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -210,7 +210,9 @@ typedef struct RelationData
struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
+ List *rd_indexprsExpand; /* expanded index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
+ List *rd_indpredExpand; /* expanded index predicate tree, if any */
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 89c27aa1..70531bb8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -58,8 +58,10 @@ extern List *RelationGetStatExtList(Relation relation);
extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
+extern List *RelationGetIndexExpressionsExpand(Relation relation);
extern List *RelationGetDummyIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetIndexPredicateExpand(Relation relation);
extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
/*
@@ -161,4 +163,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt;
/* should be used only by relcache.c and postinit.c */
extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
+extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId);
+
#endif /* RELCACHE_H */
--
2.43.0
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-07-07 13:40 =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
parent: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
0 siblings, 1 reply; 10+ messages in thread
From: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= @ 2026-07-07 13:40 UTC (permalink / raw)
To: =?utf-8?B?amlhbiBoZQ==?= <[email protected]>; =?utf-8?B?UGV0ZXIgRWlzZW50cmF1dA==?= <[email protected]>; =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]>; +Cc: =?utf-8?B?Q29yZXkgSHVpbmtlcg==?= <[email protected]>; =?utf-8?B?VG9tIExhbmU=?= <[email protected]>; =?utf-8?B?Y2hlbmdwZW5nX3lhbg==?= <[email protected]>; =?utf-8?B?Z3VvZmVuZ2xpbnV4?= <[email protected]>; =?utf-8?B?5oiR6Ieq5bex55qE6YKu566x?= <[email protected]>
Hi everyone,
This is the V4 patch. On top of prior revisions, I've revised generated_virtual.sql
together with its matching output file generated_virtual.out.
The modifications target index constraints, regular indexes, and foreign keys.
I have uncommented the existing test SQL for these modules and added a few
extra test cases. All tests are passing successfully.
regards,
--
ZizhuanLiu (X-MAN)
[email protected]
Attachments:
[application/octet-stream] v4-0001-enhance-index-support-for-virtual-generated-colum.patch (52.2K, ../../[email protected]/2-v4-0001-enhance-index-support-for-virtual-generated-colum.patch)
download | inline diff:
From 10810f5ed2513254f56f0f1d67cd739d316bc188 Mon Sep 17 00:00:00 2001
From: Zizhuan Liu <[email protected]>
Date: Tue, 7 Jul 2026 21:24:16 +0800
Subject: [PATCH v4] enhance-index-support-for-virtual-generated-columns
---
src/backend/access/heap/heapam_handler.c | 20 +-
src/backend/bootstrap/bootstrap.c | 6 +
src/backend/catalog/index.c | 61 ++++-
src/backend/catalog/indexing.c | 2 +
src/backend/catalog/toasting.c | 4 +
src/backend/commands/analyze.c | 10 +-
src/backend/commands/indexcmds.c | 42 ++--
src/backend/executor/execIndexing.c | 13 +-
src/backend/nodes/makefuncs.c | 10 +-
src/backend/optimizer/path/costsize.c | 9 +-
src/backend/optimizer/path/indxpath.c | 18 +-
src/backend/optimizer/plan/createplan.c | 6 +-
src/backend/optimizer/util/plancat.c | 22 +-
src/backend/utils/adt/selfuncs.c | 21 +-
src/backend/utils/cache/relcache.c | 126 ++++++++++
src/include/nodes/execnodes.h | 4 +
src/include/nodes/pathnodes.h | 2 +
src/include/utils/rel.h | 2 +
src/include/utils/relcache.h | 4 +
.../regress/expected/generated_virtual.out | 234 +++++++++++++++---
src/test/regress/sql/generated_virtual.sql | 88 ++++---
21 files changed, 565 insertions(+), 139 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2268cc27..22e0762e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1159,7 +1159,7 @@ heapam_index_build_range_scan(Relation heapRelation,
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
double reltuples;
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1200,7 +1200,7 @@ heapam_index_build_range_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. In a normal index build, we use
@@ -1607,9 +1607,9 @@ heapam_index_build_range_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1709,7 +1709,9 @@ heapam_index_build_range_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
return reltuples;
}
@@ -1726,7 +1728,7 @@ heapam_index_validate_scan(Relation heapRelation,
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1758,7 +1760,7 @@ heapam_index_validate_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. We need just those tuples
@@ -1895,9 +1897,9 @@ heapam_index_validate_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1952,7 +1954,9 @@ heapam_index_validate_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b0dcd987..80bf4fc4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1156,11 +1156,17 @@ index_register(Oid heap,
/* expressions will likely be null, but may as well copy it */
newind->il_info->ii_Expressions =
copyObject(indexInfo->ii_Expressions);
+ newind->il_info->ii_ExpressionsExpand =
+ copyObject(indexInfo->ii_ExpressionsExpand);
newind->il_info->ii_ExpressionsState = NIL;
+ newind->il_info->ii_ExpressionsExpandState = NIL;
/* predicate will likely be null, but may as well copy it */
newind->il_info->ii_Predicate =
copyObject(indexInfo->ii_Predicate);
+ newind->il_info->ii_PredicateExpand =
+ copyObject(indexInfo->ii_PredicateExpand);
newind->il_info->ii_PredicateState = NULL;
+ newind->il_info->ii_PredicateExpandState = NULL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357..ee260fde 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1403,6 +1403,10 @@ index_create_copy(Relation heapRelation, uint16 flags,
concurrently, /* concurrent */
indexRelation->rd_indam->amsummarizing,
oldInfo->ii_WithoutOverlaps);
+ newInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid);
+ newInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid);
/* fetch exclusion constraint info if any */
if (indexRelation->rd_index->indisexclusion)
@@ -2471,6 +2475,8 @@ BuildIndexInfo(Relation index)
false,
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
+ ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index);
+ ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
@@ -2760,10 +2766,12 @@ FormIndexDatum(IndexInfo *indexInfo,
/* First time through, set up expression evaluation state */
indexInfo->ii_ExpressionsState =
ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+ indexInfo->ii_ExpressionsExpandState =
+ ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate);
/* Check caller has set up context correctly */
Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
}
- indexpr_item = list_head(indexInfo->ii_ExpressionsState);
+ indexpr_item = list_head(indexInfo->ii_ExpressionsExpandState);
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
@@ -2775,11 +2783,40 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
- /*
- * Plain index column; get the value we need directly from the
- * heap tuple.
- */
- iDatum = slot_getattr(slot, keycol, &isNull);
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute att = TupleDescAttr(tupdesc, keycol - 1);
+
+ if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ /*
+ * Plain index column; get the value we need directly from the
+ * heap tuple.
+ */
+ iDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ TupleConstr *constr = tupdesc->constr;
+
+ for (int j = 0; j < constr->num_defval; j++)
+ {
+ AttrDefault *defval;
+ Expr *expr = NULL;
+ ExprState *exprstate;
+
+ defval = &constr->defval[j];
+ if (defval->adnum == keycol)
+ {
+ expr = stringToNode(defval->adbin);
+ exprstate = ExecPrepareExpr(expr, estate);
+ iDatum = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull);
+ break;
+ }
+ Assert(j + 1 < constr->num_defval);
+ }
+ }
}
else
{
@@ -2791,7 +2828,7 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item),
GetPerTupleExprContext(estate),
&isNull);
- indexpr_item = lnext(indexInfo->ii_ExpressionsState, indexpr_item);
+ indexpr_item = lnext(indexInfo->ii_ExpressionsExpandState, indexpr_item);
}
values[i] = iDatum;
isnull[i] = isNull;
@@ -3220,7 +3257,7 @@ IndexCheckExclusion(Relation heapRelation,
TableScanDesc scan;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -3246,7 +3283,7 @@ IndexCheckExclusion(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Scan all live tuples in the base relation.
@@ -3266,9 +3303,9 @@ IndexCheckExclusion(Relation heapRelation,
/*
* In a partial index, ignore tuples that don't satisfy the predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -3301,7 +3338,9 @@ IndexCheckExclusion(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fd7d2ec0..d27aaa09 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -133,7 +133,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
+ Assert(indexInfo->ii_ExpressionsExpand == NIL);
Assert(indexInfo->ii_Predicate == NIL);
+ Assert(indexInfo->ii_PredicateExpand == NIL);
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(index->rd_index->indimmediate);
Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 4aa52a4b..daebfda1 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -298,9 +298,13 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_IndexAttrNumbers[0] = 1;
indexInfo->ii_IndexAttrNumbers[1] = 2;
indexInfo->ii_Expressions = NIL;
+ indexInfo->ii_ExpressionsExpand = NIL;
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_Predicate = NIL;
+ indexInfo->ii_PredicateExpand = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b7..fc67c24a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -899,7 +899,7 @@ compute_index_stats(Relation onerel, double totalrows,
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
- ExprState *predicate;
+ ExprState *predicateExpand;
Datum *exprvals;
bool *exprnulls;
int numindexrows,
@@ -908,7 +908,7 @@ compute_index_stats(Relation onerel, double totalrows,
double totalindexrows;
/* Ignore index if no columns to analyze and not partial */
- if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
+ if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL)
continue;
/*
@@ -926,7 +926,7 @@ compute_index_stats(Relation onerel, double totalrows,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/* Compute and save index expression values */
exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
@@ -949,9 +949,9 @@ compute_index_stats(Relation onerel, double totalrows,
ExecStoreHeapTuple(heapTuple, slot, false);
/* If index is partial, check predicate */
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
numindexrows++;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8d..8a63efc8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -932,6 +932,8 @@ DefineIndex(ParseState *pstate,
concurrent,
amissummarizing,
stmt->iswithoutoverlaps);
+ indexInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
@@ -948,6 +950,8 @@ DefineIndex(ParseState *pstate,
root_save_userid, root_save_sec_context,
&root_save_nestlevel);
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_ExpressionsExpand, rel, InvalidOid);
/*
* Extra checks when creating a PRIMARY KEY index.
*/
@@ -1110,7 +1114,7 @@ DefineIndex(ParseState *pstate,
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
*
- * Also disallow virtual generated columns in indexes (use expression
+ * Aisallow virtual generated columns in indexes (include using expression
* index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1123,7 +1127,7 @@ DefineIndex(ParseState *pstate,
errmsg("index creation on system columns is not supported")));
- if (attno > 0 &&
+ /*if (attno > 0 && i < numberOfKeyAttributes &&
TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1131,17 +1135,15 @@ DefineIndex(ParseState *pstate,
errmsg("primary keys on virtual generated columns are not supported") :
stmt->isconstraint ?
errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported"));
+ errmsg("indexes on virtual generated columns are not supported"));*/
}
/*
- * Also check for system and generated columns used in expressions or
- * predicates.
+ * Also check for system columns used in expressions or predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
- int j;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1154,25 +1156,6 @@ DefineIndex(ParseState *pstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
-
- /*
- * XXX Virtual generated columns in index expressions or predicates
- * could be supported, but it needs support in
- * RelationGetIndexExpressions() and RelationGetIndexPredicate().
- */
- j = -1;
- while ((j = bms_next_member(indexattrs, j)) >= 0)
- {
- AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
-
- if (attno > 0 &&
- TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- stmt->isconstraint ?
- errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported")));
- }
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
@@ -1876,6 +1859,11 @@ CheckPredicate(Expr *predicate)
* If the caller switched to the table owner, ddl_userid is the role for ACL
* checks reached without traversing opaque expressions. Otherwise, it's
* InvalidOid, and other ddl_* arguments are undefined.
+ *
+ * Upon returning from this function, callers must apply
+ * ExpandVirtualGeneratedColumns() to ii_ExpressionsExpand
+ * when necessary for actual expansion if ii_ExpressionsExpand
+ * is not NIL or dummy.
*/
static void
ComputeIndexAttrs(ParseState *pstate,
@@ -2274,6 +2262,10 @@ ComputeIndexAttrs(ParseState *pstate,
attn++;
}
+
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions),
+ NULL, relId);
}
/*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index eb383812..b47daaf5 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -380,20 +380,24 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -616,20 +620,24 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1102,6 +1110,7 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
* pass hint.
*/
idxExprs = RelationGetIndexExpressions(indexRelation);
+ idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation));
hasexpression = index_expression_changed_walker((Node *) idxExprs,
allUpdatedCols);
list_free(idxExprs);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40b09958..946891b9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -828,7 +828,11 @@ make_ands_implicit(Expr *clause)
/*
* makeIndexInfo
- * create an IndexInfo node
+ * create an IndexInfo node. Upon returning from this function,
+ * callers must apply ExpandVirtualGeneratedColumns()
+ * or RelationGetIndexExpressionsExpand or RelationGetIndexPredicateExpand() to
+ * ii_ExpressionsExpand and ii_PredicateExpand as needed for actual
+ * expansion when they are not NIL or dummy.
*/
IndexInfo *
makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
@@ -856,11 +860,15 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
/* expressions */
n->ii_Expressions = expressions;
+ n->ii_ExpressionsExpand = copyObject(expressions);
n->ii_ExpressionsState = NIL;
+ n->ii_ExpressionsExpandState = NIL;
/* predicates */
n->ii_Predicate = predicates;
+ n->ii_PredicateExpand = copyObject(predicates);
n->ii_PredicateState = NULL;
+ n->ii_PredicateExpandState = NULL;
/* exclusion constraints */
n->ii_ExclusionOps = NULL;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56..34fc147a 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -594,7 +594,14 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
}
else
{
- path->path.rows = baserel->rows;
+ /*
+ * If index->predOK holds true, neither the baserel's estimated output tuples
+ * nor this index path's estimated output tuples can exceed the index's total tuples.
+ */
+ if (!index->predOK)
+ path->path.rows = baserel->rows;
+ else
+ path->path.rows = baserel->rows = index->tuples;
/* qpquals come from just the rel's restriction clauses */
qpquals = extract_nonindex_conditions(path->indexinfo->indrestrictinfo,
path->indexclauses);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3..b716ee92 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -1119,7 +1119,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
* just scanning the predOK index alone, no OR.)
*/
useful_predicate = false;
- if (index->indpred != NIL)
+ if (index->indpredExpand != NIL)
{
if (index->predOK)
{
@@ -1131,10 +1131,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
if (all_clauses == NIL)
all_clauses = list_concat_copy(clauses, other_clauses);
- if (!predicate_implied_by(index->indpred, all_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, all_clauses, false))
continue; /* can't use it at all */
- if (!predicate_implied_by(index->indpred, other_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, other_clauses, false))
useful_predicate = true;
}
}
@@ -2183,7 +2183,7 @@ find_indexpath_quals(Path *bitmapqual, List **quals, List **preds)
*quals = lappend(*quals, iclause->rinfo->clause);
}
- *preds = list_concat(*preds, ipath->indexinfo->indpred);
+ *preds = list_concat(*preds, ipath->indexinfo->indpredExpand);
}
else
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
@@ -4038,11 +4038,11 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
ListCell *lcr;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
continue; /* ignore non-partial indexes here */
if (!index->predOK) /* don't repeat work if already proven OK */
- index->predOK = predicate_implied_by(index->indpred, clauselist,
+ index->predOK = predicate_implied_by(index->indpredExpand, clauselist,
false);
/* If rel is an update target, leave indrestrictinfo as set above */
@@ -4068,7 +4068,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
/* predicate_implied_by() assumes first arg is immutable */
if (contain_mutable_functions((Node *) rinfo->clause) ||
!predicate_implied_by(list_make1(rinfo->clause),
- index->indpred, false))
+ index->indpredExpand, false))
index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
}
}
@@ -4402,14 +4402,14 @@ match_index_to_operand(Node *operand,
int i;
Node *indexkey;
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (i = 0; i < indexcol; i++)
{
if (index->indexkeys[i] == 0)
{
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
if (indexpr_item == NULL)
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183d..2bdad7bd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3340,7 +3340,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subindexECs = lappend(subindexECs, rinfo->parent_ec);
}
/* We can add any index predicate conditions, too */
- foreach(l, ipath->indexinfo->indpred)
+ foreach(l, ipath->indexinfo->indpredExpand)
{
Expr *pred = (Expr *) lfirst(l);
@@ -5142,7 +5142,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
}
/* It's an index expression, so find and cross-check the expression */
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
@@ -5167,7 +5167,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
else
elog(ERROR, "index key does not match expected index column");
}
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7c4be174..72846d9e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -433,24 +433,38 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* properly reduced.
*/
info->indexprs = RelationGetIndexExpressions(indexRelation);
+ info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation);
info->indpred = RelationGetIndexPredicate(indexRelation);
+ info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation);
if (info->indexprs)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0);
+ }
info->indexprs = (List *)
eval_const_expressions(root, (Node *) info->indexprs);
+ info->indexprsExpand = (List *)
+ eval_const_expressions(root, (Node *) info->indexprsExpand);
}
if (info->indpred)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0);
+ }
info->indpred = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(info->indpred));
+ info->indpredExpand = (List *)
+ eval_const_expressions(root,
+ (Node *) make_ands_explicit(info->indpredExpand));
info->indpred = make_ands_implicit((Expr *) info->indpred);
+ info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand);
}
/* Build targetlist using the completed indexprs data */
@@ -941,8 +955,8 @@ infer_arbiter_indexes(PlannerInfo *root)
attno - FirstLowInvalidHeapAttributeNumber);
}
- inferElems = RelationGetIndexExpressions(idxRel);
- inferIndexExprs = RelationGetIndexPredicate(idxRel);
+ inferElems = RelationGetIndexExpressionsExpand(idxRel);
+ inferIndexExprs = RelationGetIndexPredicateExpand(idxRel);
break;
}
}
@@ -1072,7 +1086,7 @@ infer_arbiter_indexes(PlannerInfo *root)
continue;
/* Expression attributes (if any) must match */
- idxExprs = RelationGetIndexExpressions(idxRel);
+ idxExprs = RelationGetIndexExpressionsExpand(idxRel);
if (idxExprs)
{
if (varno != 1)
@@ -1139,7 +1153,7 @@ infer_arbiter_indexes(PlannerInfo *root)
if (list_difference(idxExprs, inferElems) != NIL)
continue;
- predExprs = RelationGetIndexPredicate(idxRel);
+ predExprs = RelationGetIndexPredicateExpand(idxRel);
if (predExprs)
{
if (varno != 1)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d6efd070..94e21719 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -7431,6 +7431,7 @@ genericcostestimate(PlannerInfo *root,
double qual_arg_cost;
List *selectivityQuals;
ListCell *l;
+ bool needCalcIndexSelectivity = true;
/*
* If the index is partial, AND the index predicate with the explicitly
@@ -7495,9 +7496,19 @@ genericcostestimate(PlannerInfo *root,
* indexSelectivity estimate is tiny.
*/
if (numIndexTuples > index->tuples)
+ {
numIndexTuples = index->tuples;
+ needCalcIndexSelectivity = true;
+ }
+
+ /* Recalculate indexSelectivity based on the estimated numIndexTuples */
if (numIndexTuples < 1.0)
+ {
numIndexTuples = 1.0;
+ indexSelectivity = 0.0;
+ }
+ else if (needCalcIndexSelectivity && index->rel->tuples > 0)
+ indexSelectivity = numIndexTuples / index->rel->tuples;
/*
* Estimate the number of index pages that will be retrieved.
@@ -7639,10 +7650,10 @@ add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
List *predExtraQuals = NIL;
ListCell *lc;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
return indexQuals;
- foreach(lc, index->indpred)
+ foreach(lc, index->indpredExpand)
{
Node *predQual = (Node *) lfirst(lc);
List *oneQual = list_make1(predQual);
@@ -8054,6 +8065,12 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
JOIN_INNER,
NULL);
numIndexTuples = btreeSelectivity * index->rel->tuples;
+ /*
+ * The number of leaf tuples visited for cost
+ * cannot exceed the total tuples of the index.
+ */
+ if (numIndexTuples > index->tuples)
+ numIndexTuples = index->tuples;
/*
* btree automatically combines individual array element primitive
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0572ab42..e604bb91 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -91,6 +91,7 @@
#include "utils/resowner.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */
@@ -1583,7 +1584,9 @@ RelationInitIndexAccessInfo(Relation relation)
* expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
+ relation->rd_indexprsExpand = NIL;
relation->rd_indpred = NIL;
+ relation->rd_indpredExpand = NIL;
relation->rd_exclops = NULL;
relation->rd_exclprocs = NULL;
relation->rd_exclstrats = NULL;
@@ -5146,6 +5149,45 @@ RelationGetIndexExpressions(Relation relation)
return result;
}
+List *
+RelationGetIndexExpressionsExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indexprsExpand)
+ return copyObject(relation->rd_indexprsExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
+ return NIL;
+
+ result = RelationGetIndexExpressions(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indexprsExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetDummyIndexExpressions -- get dummy expressions for an index
*
@@ -5266,6 +5308,45 @@ RelationGetIndexPredicate(Relation relation)
return result;
}
+List *
+RelationGetIndexPredicateExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indpredExpand)
+ return copyObject(relation->rd_indpredExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
+ return NIL;
+
+ result = RelationGetIndexPredicate(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indpredExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
*
@@ -6490,7 +6571,9 @@ load_relcache_init_file(bool shared)
rel->rd_partcheckvalid = false;
rel->rd_partcheckcxt = NULL;
rel->rd_indexprs = NIL;
+ rel->rd_indexprsExpand = NIL;
rel->rd_indpred = NIL;
+ rel->rd_indpredExpand = NIL;
rel->rd_exclops = NULL;
rel->rd_exclprocs = NULL;
rel->rd_exclstrats = NULL;
@@ -7019,3 +7102,46 @@ ResOwnerReleaseRelation(Datum res)
RelationCloseCleanup((Relation) DatumGetPointer(res));
}
+
+List *
+ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId)
+{
+ bool opened_relation = false;
+ TupleDesc tupdesc;
+
+ if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid))
+ return list;
+
+ if (heapRelation == NULL)
+ {
+ heapRelation = table_open(heapRelId, NoLock);
+ opened_relation = true;
+ }
+
+ tupdesc = RelationGetDescr(heapRelation);
+ if ((tupdesc->constr && tupdesc->constr->has_generated_virtual))
+ {
+ int j;
+ Bitmapset *indexattrs = NULL;
+
+ pull_varattnos((Node *)list, 1, &indexattrs);
+
+ j = -1;
+ while ((j = bms_next_member(indexattrs, j)) >= 0)
+ {
+ AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno > 0 &&
+ TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1);
+ break;
+ }
+ }
+ }
+
+ if (opened_relation)
+ table_close(heapRelation, NoLock);
+
+ return list;
+}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 53c13831..20a2e618 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -190,13 +190,17 @@ typedef struct IndexInfo
/* expr trees for expression entries, or NIL if none */
List *ii_Expressions; /* list of Expr */
+ List *ii_ExpressionsExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
List *ii_ExpressionsState; /* list of ExprState */
+ List *ii_ExpressionsExpandState; /* list of ExprState */
/* partial-index predicate, or NIL if none */
List *ii_Predicate; /* list of Expr */
+ List *ii_PredicateExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
ExprState *ii_PredicateState;
+ ExprState *ii_PredicateExpandState;
/* Per-column exclusion operators, or NULL if none */
Oid *ii_ExclusionOps; /* array with one entry per column */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c681..9c65e123 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,8 +1403,10 @@ typedef struct IndexOptInfo
* print indextlist
*/
List *indexprs pg_node_attr(read_write_ignore);
+ List *indexprsExpand pg_node_attr(read_write_ignore);
/* predicate if a partial index, else NIL */
List *indpred;
+ List *indpredExpand;
/* targetlist representing index columns */
List *indextlist;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa07ebf8..7bd01b36 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -210,7 +210,9 @@ typedef struct RelationData
struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
+ List *rd_indexprsExpand; /* expanded index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
+ List *rd_indpredExpand; /* expanded index predicate tree, if any */
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 89c27aa1..70531bb8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -58,8 +58,10 @@ extern List *RelationGetStatExtList(Relation relation);
extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
+extern List *RelationGetIndexExpressionsExpand(Relation relation);
extern List *RelationGetDummyIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetIndexPredicateExpand(Relation relation);
extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
/*
@@ -161,4 +163,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt;
/* should be used only by relcache.c and postinit.c */
extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
+extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId);
+
#endif /* RELCACHE_H */
diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out
index 24d5dbf4..45ee5307 100644
--- a/src/test/regress/expected/generated_virtual.out
+++ b/src/test/regress/expected/generated_virtual.out
@@ -370,6 +370,7 @@ Not-null constraints:
"gtest1_a_not_null" NOT NULL "a" (inherited)
Inherits:
gtest1
+Access method: heap
INSERT INTO gtestx (a, x) VALUES (11, 22);
SELECT * FROM gtest1;
@@ -763,39 +764,202 @@ ERROR: column "c" of relation "gtestnn_child" contains null values
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok
-- index constraints
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE);
-ERROR: unique constraints on virtual generated columns are not supported
---INSERT INTO gtest22a VALUES (2);
---INSERT INTO gtest22a VALUES (3);
---INSERT INTO gtest22a VALUES (4);
+INSERT INTO gtest22a VALUES (2); -- ok
+INSERT INTO gtest22a VALUES (3); -- error
+ERROR: duplicate key value violates unique constraint "gtest22a_b_key"
+DETAIL: Key (b)=(1) already exists.
+INSERT INTO gtest22a VALUES (4); -- ok
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b));
-ERROR: primary keys on virtual generated columns are not supported
---INSERT INTO gtest22b VALUES (2);
---INSERT INTO gtest22b VALUES (2);
+INSERT INTO gtest22b VALUES (2); -- ok
+INSERT INTO gtest22b VALUES (2); -- error
+ERROR: duplicate key value violates unique constraint "gtest22b_pkey"
+DETAIL: Key (a, b)=(2, 1) already exists.
-- indexes
CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
---CREATE INDEX gtest22c_b_idx ON gtest22c (b);
---CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
---CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
---\d gtest22c
---INSERT INTO gtest22c VALUES (1), (2), (3);
---SET enable_seqscan TO off;
---SET enable_bitmapscan TO off;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
---SELECT * FROM gtest22c WHERE b = 4;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
---SELECT * FROM gtest22c WHERE b * 3 = 6;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
---ANALYZE gtest22c;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
---SELECT * FROM gtest22c WHERE b = 8;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
---SELECT * FROM gtest22c WHERE b * 3 = 12;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---RESET enable_seqscan;
---RESET enable_bitmapscan;
+CREATE INDEX gtest22c_b_idx ON gtest22c (b); -- ok
+CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -- ok
+CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -- ok
+\d gtest22c
+ Table "generated_virtual_tests.gtest22c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+-----------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2)
+Indexes:
+ "gtest22c_b_idx" btree (b)
+ "gtest22c_expr_idx" btree ((b * 3))
+ "gtest22c_pred_idx" btree (a) WHERE b > 0
+
+INSERT INTO gtest22c VALUES (1), (2), (3);
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Filter: ((a * 2) = 4)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 4;
+ a | b
+---+---
+ 2 | 4
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
+ QUERY PLAN
+------------------------------------------------
+ Index Scan using gtest22c_expr_idx on gtest22c
+ Index Cond: (((a * 2) * 3) = 6)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b * 3 = 6;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Index Cond: (a = 1)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
+ANALYZE gtest22c;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Filter: ((a * 4) = 8)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 8;
+ a | b
+---+---
+ 2 | 8
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
+ QUERY PLAN
+------------------------------------------------
+ Index Scan using gtest22c_expr_idx on gtest22c
+ Index Cond: (((a * 4) * 3) = 12)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b * 3 = 12;
+ a | b
+---+---
+ 1 | 4
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Index Cond: (a = 1)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ a | b
+---+---
+ 1 | 4
+(1 row)
+
+CREATE TABLE gtest22d (a int
+ ,v1 int GENERATED ALWAYS AS (a * 11) VIRTUAL
+ ,v2 int GENERATED ALWAYS AS (a * 22) VIRTUAL
+ ,v3 int GENERATED ALWAYS AS (a * 33) VIRTUAL
+ ,v4 int GENERATED ALWAYS AS (a * 44) VIRTUAL
+); -- ok
+insert into gtest22d select oid from pg_catalog.pg_class union all select -1; -- ok
+CREATE INDEX gtest22d_vgc_idx ON gtest22d(a,v1,abs(v2)) Include(v3) WHERE v4 < 100; -- ok
+\d gtest22d
+ Table "generated_virtual_tests.gtest22d"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------
+ a | integer | | |
+ v1 | integer | | | generated always as (a * 11)
+ v2 | integer | | | generated always as (a * 22)
+ v3 | integer | | | generated always as (a * 33)
+ v4 | integer | | | generated always as (a * 44)
+Indexes:
+ "gtest22d_vgc_idx" btree (a, v1, abs(v2)) INCLUDE (v3) WHERE v4 < 100
+
+analyze gtest22d;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 < 100; --Index Only Scan or Index Scan
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using gtest22d_vgc_idx on gtest22d
+(1 row)
+
+SELECT * FROM gtest22d WHERE v4 < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 * 5 < 100; --Seq Scan
+ QUERY PLAN
+----------------------------------
+ Seq Scan on gtest22d
+ Disabled: true
+ Filter: (((a * 44) * 5) < 100)
+(3 rows)
+
+SELECT * FROM gtest22d WHERE v4 * 5 < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE a * 44 < 100; --Index Only Scan or Index Scan
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using gtest22d_vgc_idx on gtest22d
+(1 row)
+
+SELECT * FROM gtest22d WHERE a * 44 < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 44) < 100; --Index Only Scan or Index Scan
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using gtest22d_vgc_idx on gtest22d
+(1 row)
+
+SELECT * FROM gtest22d WHERE (a * 44) < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 33) < 100; --Seq Scan
+ QUERY PLAN
+----------------------------
+ Seq Scan on gtest22d
+ Disabled: true
+ Filter: ((a * 33) < 100)
+(3 rows)
+
+SELECT * FROM gtest22d WHERE (a * 33) < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
-- foreign keys
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
--INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33);
@@ -813,12 +977,12 @@ ERROR: foreign key constraints on virtual generated columns are not supported
--DROP TABLE gtest23b;
--DROP TABLE gtest23a;
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y));
-ERROR: primary keys on virtual generated columns are not supported
---INSERT INTO gtest23p VALUES (1), (2), (3);
+INSERT INTO gtest23p VALUES (1), (2), (3); -- ok
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
-ERROR: relation "gtest23p" does not exist
---INSERT INTO gtest23q VALUES (1, 2); -- ok
---INSERT INTO gtest23q VALUES (2, 5); -- error
+INSERT INTO gtest23q VALUES (1, 2); -- ok
+INSERT INTO gtest23q VALUES (2, 5); -- error
+ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey"
+DETAIL: Key (b)=(5) is not present in table "gtest23p".
-- domains
CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL);
diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql
index 9c2bb659..5ebdc5ef 100644
--- a/src/test/regress/sql/generated_virtual.sql
+++ b/src/test/regress/sql/generated_virtual.sql
@@ -396,40 +396,62 @@ ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif
-- index constraints
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE);
---INSERT INTO gtest22a VALUES (2);
---INSERT INTO gtest22a VALUES (3);
---INSERT INTO gtest22a VALUES (4);
+INSERT INTO gtest22a VALUES (2); -- ok
+INSERT INTO gtest22a VALUES (3); -- error
+INSERT INTO gtest22a VALUES (4); -- ok
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b));
---INSERT INTO gtest22b VALUES (2);
---INSERT INTO gtest22b VALUES (2);
+INSERT INTO gtest22b VALUES (2); -- ok
+INSERT INTO gtest22b VALUES (2); -- error
-- indexes
CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
---CREATE INDEX gtest22c_b_idx ON gtest22c (b);
---CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
---CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
---\d gtest22c
-
---INSERT INTO gtest22c VALUES (1), (2), (3);
---SET enable_seqscan TO off;
---SET enable_bitmapscan TO off;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
---SELECT * FROM gtest22c WHERE b = 4;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
---SELECT * FROM gtest22c WHERE b * 3 = 6;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
-
---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
---ANALYZE gtest22c;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
---SELECT * FROM gtest22c WHERE b = 8;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
---SELECT * FROM gtest22c WHERE b * 3 = 12;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---RESET enable_seqscan;
---RESET enable_bitmapscan;
+CREATE INDEX gtest22c_b_idx ON gtest22c (b); -- ok
+CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -- ok
+CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -- ok
+\d gtest22c
+
+INSERT INTO gtest22c VALUES (1), (2), (3);
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
+SELECT * FROM gtest22c WHERE b = 4;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
+SELECT * FROM gtest22c WHERE b * 3 = 6;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+
+ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
+ANALYZE gtest22c;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
+SELECT * FROM gtest22c WHERE b = 8;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
+SELECT * FROM gtest22c WHERE b * 3 = 12;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+
+CREATE TABLE gtest22d (a int
+ ,v1 int GENERATED ALWAYS AS (a * 11) VIRTUAL
+ ,v2 int GENERATED ALWAYS AS (a * 22) VIRTUAL
+ ,v3 int GENERATED ALWAYS AS (a * 33) VIRTUAL
+ ,v4 int GENERATED ALWAYS AS (a * 44) VIRTUAL
+); -- ok
+insert into gtest22d select oid from pg_catalog.pg_class union all select -1; -- ok
+CREATE INDEX gtest22d_vgc_idx ON gtest22d(a,v1,abs(v2)) Include(v3) WHERE v4 < 100; -- ok
+\d gtest22d
+analyze gtest22d;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 < 100; --Index Only Scan or Index Scan
+SELECT * FROM gtest22d WHERE v4 < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 * 5 < 100; --Seq Scan
+SELECT * FROM gtest22d WHERE v4 * 5 < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE a * 44 < 100; --Index Only Scan or Index Scan
+SELECT * FROM gtest22d WHERE a * 44 < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 44) < 100; --Index Only Scan or Index Scan
+SELECT * FROM gtest22d WHERE (a * 44) < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 33) < 100; --Seq Scan
+SELECT * FROM gtest22d WHERE (a * 33) < 100;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
-- foreign keys
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
@@ -450,11 +472,11 @@ CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRT
--DROP TABLE gtest23a;
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y));
---INSERT INTO gtest23p VALUES (1), (2), (3);
+INSERT INTO gtest23p VALUES (1), (2), (3); -- ok
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
---INSERT INTO gtest23q VALUES (1, 2); -- ok
---INSERT INTO gtest23q VALUES (2, 5); -- error
+INSERT INTO gtest23q VALUES (1, 2); -- ok
+INSERT INTO gtest23q VALUES (2, 5); -- error
-- domains
CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
--
2.43.0
^ permalink raw reply [nested|flat] 10+ messages in thread
* Re: support create index on virtual generated column.
@ 2026-07-08 10:02 =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
parent: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
0 siblings, 0 replies; 10+ messages in thread
From: =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= @ 2026-07-08 10:02 UTC (permalink / raw)
To: =?utf-8?B?amlhbiBoZQ==?= <[email protected]>; =?utf-8?B?UGV0ZXIgRWlzZW50cmF1dA==?= <[email protected]>; =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]>; +Cc: =?utf-8?B?Q29yZXkgSHVpbmtlcg==?= <[email protected]>; =?utf-8?B?VG9tIExhbmU=?= <[email protected]>; =?utf-8?B?Y2hlbmdwZW5nX3lhbg==?= <[email protected]>; =?utf-8?B?Z3VvZmVuZ2xpbnV4?= <[email protected]>; =?utf-8?B?5oiR6Ieq5bex55qE6YKu566x?= <[email protected]>
Hi everyone,
CFBot reported V4 patch mismatched results for CREATE INDEX compared with previous runs.
I’ve conducted tests and revised the relevant code accordingly:
1.Modified genericcostestimate() in src/backend/utils/adt/selfuncs.c to calculate indexSelectivity
based on the estimated numIndexTuples.
2.Updated cost_index() located in src/backend/optimizer/path/costsize.c: when index->predOK
evaluates to true, temporarily assign baserel->rows to index->tuples, and restore the original value
before exiting the function.
I have carried out corresponding test cases. After these changes, PostgreSQL can properly rebuild indexes
and correctly compute the rows and column data that the rebuilt index should contain based on the new
expressions when executing related statements:
```SQL
xman5=# \d+ t1
Table "public.t1"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+------------------------------------+---------+-------------+--------------+-------------
a | integer | | | | plain | | |
s1 | integer | | | generated always as (a * 1) stored | plain | | |
s2 | integer | | | generated always as (a * 2) stored | plain | | |
s3 | integer | | | generated always as (a * 3) stored | plain | | |
s4 | integer | | | generated always as (a * 4) stored | plain | | |
v1 | integer | | | generated always as (a * 11) | plain | | |
v2 | integer | | | generated always as (a * 22) | plain | | |
v3 | integer | | | generated always as (a * 33) | plain | | |
v4 | integer | | | generated always as (a * 44) | plain | | |
Indexes:
"idx_t1_s" btree (a, s1, abs(s2)) INCLUDE (s3) WHERE s4 < 100
"idx_t1_v" btree (a, v1, abs(v2)) INCLUDE (v3) WHERE v4 < 100
Access method: heap
update t1 set a = -2;
update t1 set a = -1;
ALTER TABLE t1 ALTER COLUMN v1 SET EXPRESSION AS (a * 110);
ALTER TABLE t1 ALTER COLUMN v2 SET EXPRESSION AS (a * 220);
ALTER TABLE t1 ALTER COLUMN v3 SET EXPRESSION AS (a * -330);
ALTER TABLE t1 ALTER COLUMN v4 SET EXPRESSION AS (a * -440);
```SQL
regards,
--
ZizhuanLiu (X-MAN)
[email protected]
Attachments:
[application/octet-stream] v5-0001-v5-enhance-index-support-for-virtual-generated-co.patch (54.0K, ../../[email protected]/2-v5-0001-v5-enhance-index-support-for-virtual-generated-co.patch)
download | inline diff:
From b4185004901cae552ca15d1b60849453124c3e08 Mon Sep 17 00:00:00 2001
From: Zizhuan Liu <[email protected]>
Date: Wed, 8 Jul 2026 17:43:01 +0800
Subject: [PATCH v5] v5 enhance-index-support-for-virtual-generated-columns
---
src/backend/access/heap/heapam_handler.c | 20 +-
src/backend/bootstrap/bootstrap.c | 6 +
src/backend/catalog/index.c | 61 ++++-
src/backend/catalog/indexing.c | 2 +
src/backend/catalog/toasting.c | 4 +
src/backend/commands/analyze.c | 10 +-
src/backend/commands/indexcmds.c | 42 ++--
src/backend/executor/execIndexing.c | 13 +-
src/backend/nodes/makefuncs.c | 10 +-
src/backend/optimizer/path/costsize.c | 17 +-
src/backend/optimizer/path/indxpath.c | 22 +-
src/backend/optimizer/plan/createplan.c | 6 +-
src/backend/optimizer/util/plancat.c | 22 +-
src/backend/utils/adt/selfuncs.c | 25 +-
src/backend/utils/cache/relcache.c | 126 ++++++++++
src/include/nodes/execnodes.h | 4 +
src/include/nodes/pathnodes.h | 2 +
src/include/utils/rel.h | 2 +
src/include/utils/relcache.h | 4 +
.../regress/expected/generated_virtual.out | 234 +++++++++++++++---
src/test/regress/sql/generated_virtual.sql | 88 ++++---
21 files changed, 578 insertions(+), 142 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2268cc27..22e0762e 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -1159,7 +1159,7 @@ heapam_index_build_range_scan(Relation heapRelation,
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
double reltuples;
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1200,7 +1200,7 @@ heapam_index_build_range_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. In a normal index build, we use
@@ -1607,9 +1607,9 @@ heapam_index_build_range_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1709,7 +1709,9 @@ heapam_index_build_range_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
return reltuples;
}
@@ -1726,7 +1728,7 @@ heapam_index_validate_scan(Relation heapRelation,
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -1758,7 +1760,7 @@ heapam_index_validate_scan(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Prepare for scan of the base relation. We need just those tuples
@@ -1895,9 +1897,9 @@ heapam_index_validate_scan(Relation heapRelation,
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1952,7 +1954,9 @@ heapam_index_validate_scan(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index b0dcd987..80bf4fc4 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -1156,11 +1156,17 @@ index_register(Oid heap,
/* expressions will likely be null, but may as well copy it */
newind->il_info->ii_Expressions =
copyObject(indexInfo->ii_Expressions);
+ newind->il_info->ii_ExpressionsExpand =
+ copyObject(indexInfo->ii_ExpressionsExpand);
newind->il_info->ii_ExpressionsState = NIL;
+ newind->il_info->ii_ExpressionsExpandState = NIL;
/* predicate will likely be null, but may as well copy it */
newind->il_info->ii_Predicate =
copyObject(indexInfo->ii_Predicate);
+ newind->il_info->ii_PredicateExpand =
+ copyObject(indexInfo->ii_PredicateExpand);
newind->il_info->ii_PredicateState = NULL;
+ newind->il_info->ii_PredicateExpandState = NULL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357..ee260fde 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1403,6 +1403,10 @@ index_create_copy(Relation heapRelation, uint16 flags,
concurrently, /* concurrent */
indexRelation->rd_indam->amsummarizing,
oldInfo->ii_WithoutOverlaps);
+ newInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_ExpressionsExpand, heapRelation, InvalidOid);
+ newInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(newInfo->ii_PredicateExpand, heapRelation, InvalidOid);
/* fetch exclusion constraint info if any */
if (indexRelation->rd_index->indisexclusion)
@@ -2471,6 +2475,8 @@ BuildIndexInfo(Relation index)
false,
index->rd_indam->amsummarizing,
indexStruct->indisexclusion && indexStruct->indisunique);
+ ii->ii_ExpressionsExpand = (List *)RelationGetIndexExpressionsExpand(index);
+ ii->ii_PredicateExpand = (List *)RelationGetIndexPredicateExpand(index);
/* fill in attribute numbers */
for (i = 0; i < numAtts; i++)
@@ -2760,10 +2766,12 @@ FormIndexDatum(IndexInfo *indexInfo,
/* First time through, set up expression evaluation state */
indexInfo->ii_ExpressionsState =
ExecPrepareExprList(indexInfo->ii_Expressions, estate);
+ indexInfo->ii_ExpressionsExpandState =
+ ExecPrepareExprList(indexInfo->ii_ExpressionsExpand, estate);
/* Check caller has set up context correctly */
Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
}
- indexpr_item = list_head(indexInfo->ii_ExpressionsState);
+ indexpr_item = list_head(indexInfo->ii_ExpressionsExpandState);
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
{
@@ -2775,11 +2783,40 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = slot_getsysattr(slot, keycol, &isNull);
else if (keycol != 0)
{
- /*
- * Plain index column; get the value we need directly from the
- * heap tuple.
- */
- iDatum = slot_getattr(slot, keycol, &isNull);
+ TupleDesc tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute att = TupleDescAttr(tupdesc, keycol - 1);
+
+ if (att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ /*
+ * Plain index column; get the value we need directly from the
+ * heap tuple.
+ */
+ iDatum = slot_getattr(slot, keycol, &isNull);
+ }
+ else
+ {
+ TupleConstr *constr = tupdesc->constr;
+
+ for (int j = 0; j < constr->num_defval; j++)
+ {
+ AttrDefault *defval;
+ Expr *expr = NULL;
+ ExprState *exprstate;
+
+ defval = &constr->defval[j];
+ if (defval->adnum == keycol)
+ {
+ expr = stringToNode(defval->adbin);
+ exprstate = ExecPrepareExpr(expr, estate);
+ iDatum = ExecEvalExprSwitchContext(exprstate,
+ GetPerTupleExprContext(estate),
+ &isNull);
+ break;
+ }
+ Assert(j + 1 < constr->num_defval);
+ }
+ }
}
else
{
@@ -2791,7 +2828,7 @@ FormIndexDatum(IndexInfo *indexInfo,
iDatum = ExecEvalExprSwitchContext((ExprState *) lfirst(indexpr_item),
GetPerTupleExprContext(estate),
&isNull);
- indexpr_item = lnext(indexInfo->ii_ExpressionsState, indexpr_item);
+ indexpr_item = lnext(indexInfo->ii_ExpressionsExpandState, indexpr_item);
}
values[i] = iDatum;
isnull[i] = isNull;
@@ -3220,7 +3257,7 @@ IndexCheckExclusion(Relation heapRelation,
TableScanDesc scan;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- ExprState *predicate;
+ ExprState *predicateExpand;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
@@ -3246,7 +3283,7 @@ IndexCheckExclusion(Relation heapRelation,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/*
* Scan all live tuples in the base relation.
@@ -3266,9 +3303,9 @@ IndexCheckExclusion(Relation heapRelation,
/*
* In a partial index, ignore tuples that don't satisfy the predicate.
*/
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -3301,7 +3338,9 @@ IndexCheckExclusion(Relation heapRelation,
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
}
/*
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index fd7d2ec0..d27aaa09 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -133,7 +133,9 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
* supported, nor exclusion constraints, nor deferred uniqueness
*/
Assert(indexInfo->ii_Expressions == NIL);
+ Assert(indexInfo->ii_ExpressionsExpand == NIL);
Assert(indexInfo->ii_Predicate == NIL);
+ Assert(indexInfo->ii_PredicateExpand == NIL);
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(index->rd_index->indimmediate);
Assert(indexInfo->ii_NumIndexKeyAttrs != 0);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 4aa52a4b..daebfda1 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -298,9 +298,13 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_IndexAttrNumbers[0] = 1;
indexInfo->ii_IndexAttrNumbers[1] = 2;
indexInfo->ii_Expressions = NIL;
+ indexInfo->ii_ExpressionsExpand = NIL;
indexInfo->ii_ExpressionsState = NIL;
+ indexInfo->ii_ExpressionsExpandState = NIL;
indexInfo->ii_Predicate = NIL;
+ indexInfo->ii_PredicateExpand = NIL;
indexInfo->ii_PredicateState = NULL;
+ indexInfo->ii_PredicateExpandState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b7..fc67c24a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -899,7 +899,7 @@ compute_index_stats(Relation onerel, double totalrows,
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
- ExprState *predicate;
+ ExprState *predicateExpand;
Datum *exprvals;
bool *exprnulls;
int numindexrows,
@@ -908,7 +908,7 @@ compute_index_stats(Relation onerel, double totalrows,
double totalindexrows;
/* Ignore index if no columns to analyze and not partial */
- if (attr_cnt == 0 && indexInfo->ii_Predicate == NIL)
+ if (attr_cnt == 0 && indexInfo->ii_PredicateExpand == NIL)
continue;
/*
@@ -926,7 +926,7 @@ compute_index_stats(Relation onerel, double totalrows,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate. */
- predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
/* Compute and save index expression values */
exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
@@ -949,9 +949,9 @@ compute_index_stats(Relation onerel, double totalrows,
ExecStoreHeapTuple(heapTuple, slot, false);
/* If index is partial, check predicate */
- if (predicate != NULL)
+ if (predicateExpand != NULL)
{
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
numindexrows++;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8d..8a63efc8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -932,6 +932,8 @@ DefineIndex(ParseState *pstate,
concurrent,
amissummarizing,
stmt->iswithoutoverlaps);
+ indexInfo->ii_PredicateExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_PredicateExpand, rel, InvalidOid);
typeIds = palloc_array(Oid, numberOfAttributes);
collationIds = palloc_array(Oid, numberOfAttributes);
@@ -948,6 +950,8 @@ DefineIndex(ParseState *pstate,
root_save_userid, root_save_sec_context,
&root_save_nestlevel);
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(indexInfo->ii_ExpressionsExpand, rel, InvalidOid);
/*
* Extra checks when creating a PRIMARY KEY index.
*/
@@ -1110,7 +1114,7 @@ DefineIndex(ParseState *pstate,
* We disallow indexes on system columns. They would not necessarily get
* updated correctly, and they don't seem useful anyway.
*
- * Also disallow virtual generated columns in indexes (use expression
+ * Aisallow virtual generated columns in indexes (include using expression
* index instead).
*/
for (int i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1123,7 +1127,7 @@ DefineIndex(ParseState *pstate,
errmsg("index creation on system columns is not supported")));
- if (attno > 0 &&
+ /*if (attno > 0 && i < numberOfKeyAttributes &&
TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
ereport(ERROR,
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1131,17 +1135,15 @@ DefineIndex(ParseState *pstate,
errmsg("primary keys on virtual generated columns are not supported") :
stmt->isconstraint ?
errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported"));
+ errmsg("indexes on virtual generated columns are not supported"));*/
}
/*
- * Also check for system and generated columns used in expressions or
- * predicates.
+ * Also check for system columns used in expressions or predicates.
*/
if (indexInfo->ii_Expressions || indexInfo->ii_Predicate)
{
Bitmapset *indexattrs = NULL;
- int j;
pull_varattnos((Node *) indexInfo->ii_Expressions, 1, &indexattrs);
pull_varattnos((Node *) indexInfo->ii_Predicate, 1, &indexattrs);
@@ -1154,25 +1156,6 @@ DefineIndex(ParseState *pstate,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("index creation on system columns is not supported")));
}
-
- /*
- * XXX Virtual generated columns in index expressions or predicates
- * could be supported, but it needs support in
- * RelationGetIndexExpressions() and RelationGetIndexPredicate().
- */
- j = -1;
- while ((j = bms_next_member(indexattrs, j)) >= 0)
- {
- AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
-
- if (attno > 0 &&
- TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- stmt->isconstraint ?
- errmsg("unique constraints on virtual generated columns are not supported") :
- errmsg("indexes on virtual generated columns are not supported")));
- }
}
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
@@ -1876,6 +1859,11 @@ CheckPredicate(Expr *predicate)
* If the caller switched to the table owner, ddl_userid is the role for ACL
* checks reached without traversing opaque expressions. Otherwise, it's
* InvalidOid, and other ddl_* arguments are undefined.
+ *
+ * Upon returning from this function, callers must apply
+ * ExpandVirtualGeneratedColumns() to ii_ExpressionsExpand
+ * when necessary for actual expansion if ii_ExpressionsExpand
+ * is not NIL or dummy.
*/
static void
ComputeIndexAttrs(ParseState *pstate,
@@ -2274,6 +2262,10 @@ ComputeIndexAttrs(ParseState *pstate,
attn++;
}
+
+ indexInfo->ii_ExpressionsExpand =
+ ExpandVirtualGeneratedColumns(copyObject(indexInfo->ii_Expressions),
+ NULL, relId);
}
/*
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index eb383812..b47daaf5 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -380,20 +380,24 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -616,20 +620,24 @@ ExecCheckIndexConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
if (indexInfo->ii_Predicate != NIL)
{
ExprState *predicate;
+ ExprState *predicateExpand;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
+ predicateExpand = indexInfo->ii_PredicateExpandState;
if (predicate == NULL)
{
predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
+ predicateExpand = ExecPrepareQual(indexInfo->ii_PredicateExpand, estate);
indexInfo->ii_PredicateState = predicate;
+ indexInfo->ii_PredicateExpandState = predicateExpand;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext))
+ if (!ExecQual(predicateExpand, econtext))
continue;
}
@@ -1102,6 +1110,7 @@ index_unchanged_by_update(ResultRelInfo *resultRelInfo, EState *estate,
* pass hint.
*/
idxExprs = RelationGetIndexExpressions(indexRelation);
+ idxExprs = list_concat(idxExprs, RelationGetIndexExpressionsExpand(indexRelation));
hasexpression = index_expression_changed_walker((Node *) idxExprs,
allUpdatedCols);
list_free(idxExprs);
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 40b09958..3aac6b51 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -828,7 +828,11 @@ make_ands_implicit(Expr *clause)
/*
* makeIndexInfo
- * create an IndexInfo node
+ * create an IndexInfo node. Upon returning from this function,
+ * callers must apply ExpandVirtualGeneratedColumns()
+ * or RelationGetIndexExpressionsExpand or RelationGetIndexPredicateExpand() to
+ * ii_ExpressionsExpand and ii_PredicateExpand as needed for actual
+ * expansion when they are not NIL or dummy.
*/
IndexInfo *
makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
@@ -856,11 +860,15 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions,
/* expressions */
n->ii_Expressions = expressions;
+ n->ii_ExpressionsExpand = copyObject(expressions);
n->ii_ExpressionsState = NIL;
+ n->ii_ExpressionsExpandState = NIL;
/* predicates */
n->ii_Predicate = predicates;
+ n->ii_PredicateExpand = copyObject(predicates);
n->ii_PredicateState = NULL;
+ n->ii_PredicateExpandState = NULL;
/* exclusion constraints */
n->ii_ExclusionOps = NULL;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 1c575e56..be5c2e69 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -569,6 +569,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
double rand_heap_pages;
double index_pages;
uint64 enable_mask;
+ Cardinality saveBaserelRows;
/* Should only be applied to base relations */
Assert(IsA(baserel, RelOptInfo) &&
@@ -583,6 +584,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
* baserestrictinfo as the list of relevant restriction clauses for the
* rel.
*/
+ saveBaserelRows = baserel->rows;
if (path->path.param_info)
{
path->path.rows = path->path.param_info->ppi_rows;
@@ -594,7 +596,14 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
}
else
{
- path->path.rows = baserel->rows;
+ /*
+ * If index->predOK holds true, neither the baserel's estimated output tuples
+ * nor this index path's estimated output tuples can exceed the index's total tuples.
+ */
+ if (!index->predOK)
+ path->path.rows = baserel->rows;
+ else
+ path->path.rows = baserel->rows = index->tuples;
/* qpquals come from just the rel's restriction clauses */
qpquals = extract_nonindex_conditions(path->indexinfo->indrestrictinfo,
path->indexclauses);
@@ -772,8 +781,10 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
* doing extra computation.
*/
if (path->path.parallel_workers <= 0)
+ {
+ baserel->rows = saveBaserelRows;
return;
-
+ }
path->path.parallel_aware = true;
}
@@ -817,6 +828,8 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
path->path.startup_cost = startup_cost;
path->path.total_cost = startup_cost + run_cost;
+
+ baserel->rows = saveBaserelRows;
}
/*
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3f5d4fa3..23117e4a 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -267,7 +267,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
* (generate_bitmap_or_paths() might be able to do something with
* them, but that's of no concern here.)
*/
- if (index->indpred != NIL && !index->predOK)
+ if (index->indpredExpand != NIL && !index->predOK)
continue;
/*
@@ -1119,7 +1119,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
* just scanning the predOK index alone, no OR.)
*/
useful_predicate = false;
- if (index->indpred != NIL)
+ if (index->indpredExpand != NIL)
{
if (index->predOK)
{
@@ -1131,10 +1131,10 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
if (all_clauses == NIL)
all_clauses = list_concat_copy(clauses, other_clauses);
- if (!predicate_implied_by(index->indpred, all_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, all_clauses, false))
continue; /* can't use it at all */
- if (!predicate_implied_by(index->indpred, other_clauses, false))
+ if (!predicate_implied_by(index->indpredExpand, other_clauses, false))
useful_predicate = true;
}
}
@@ -2183,7 +2183,7 @@ find_indexpath_quals(Path *bitmapqual, List **quals, List **preds)
*quals = lappend(*quals, iclause->rinfo->clause);
}
- *preds = list_concat(*preds, ipath->indexinfo->indpred);
+ *preds = list_concat(*preds, ipath->indexinfo->indpredExpand);
}
else
elog(ERROR, "unrecognized node type: %d", nodeTag(bitmapqual));
@@ -3959,7 +3959,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
index->indrestrictinfo = rel->baserestrictinfo;
- if (index->indpred)
+ if (index->indpredExpand)
have_partial = true;
}
if (!have_partial)
@@ -4038,11 +4038,11 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
ListCell *lcr;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
continue; /* ignore non-partial indexes here */
if (!index->predOK) /* don't repeat work if already proven OK */
- index->predOK = predicate_implied_by(index->indpred, clauselist,
+ index->predOK = predicate_implied_by(index->indpredExpand, clauselist,
false);
/* If rel is an update target, leave indrestrictinfo as set above */
@@ -4068,7 +4068,7 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
/* predicate_implied_by() assumes first arg is immutable */
if (contain_mutable_functions((Node *) rinfo->clause) ||
!predicate_implied_by(list_make1(rinfo->clause),
- index->indpred, false))
+ index->indpredExpand, false))
index->indrestrictinfo = lappend(index->indrestrictinfo, rinfo);
}
}
@@ -4402,14 +4402,14 @@ match_index_to_operand(Node *operand,
int i;
Node *indexkey;
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (i = 0; i < indexcol; i++)
{
if (index->indexkeys[i] == 0)
{
if (indexpr_item == NULL)
elog(ERROR, "wrong number of index expressions");
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
if (indexpr_item == NULL)
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index de6a183d..2bdad7bd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3340,7 +3340,7 @@ create_bitmap_subplan(PlannerInfo *root, Path *bitmapqual,
subindexECs = lappend(subindexECs, rinfo->parent_ec);
}
/* We can add any index predicate conditions, too */
- foreach(l, ipath->indexinfo->indpred)
+ foreach(l, ipath->indexinfo->indpredExpand)
{
Expr *pred = (Expr *) lfirst(l);
@@ -5142,7 +5142,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
}
/* It's an index expression, so find and cross-check the expression */
- indexpr_item = list_head(index->indexprs);
+ indexpr_item = list_head(index->indexprsExpand);
for (pos = 0; pos < index->ncolumns; pos++)
{
if (index->indexkeys[pos] == 0)
@@ -5167,7 +5167,7 @@ fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
else
elog(ERROR, "index key does not match expected index column");
}
- indexpr_item = lnext(index->indexprs, indexpr_item);
+ indexpr_item = lnext(index->indexprsExpand, indexpr_item);
}
}
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 7c4be174..72846d9e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -433,24 +433,38 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
* properly reduced.
*/
info->indexprs = RelationGetIndexExpressions(indexRelation);
+ info->indexprsExpand = RelationGetIndexExpressionsExpand(indexRelation);
info->indpred = RelationGetIndexPredicate(indexRelation);
+ info->indpredExpand = RelationGetIndexPredicateExpand(indexRelation);
if (info->indexprs)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indexprsExpand, 1, varno, 0);
+ }
info->indexprs = (List *)
eval_const_expressions(root, (Node *) info->indexprs);
+ info->indexprsExpand = (List *)
+ eval_const_expressions(root, (Node *) info->indexprsExpand);
}
if (info->indpred)
{
if (varno != 1)
+ {
ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
+ ChangeVarNodes((Node *) info->indpredExpand, 1, varno, 0);
+ }
info->indpred = (List *)
eval_const_expressions(root,
(Node *) make_ands_explicit(info->indpred));
+ info->indpredExpand = (List *)
+ eval_const_expressions(root,
+ (Node *) make_ands_explicit(info->indpredExpand));
info->indpred = make_ands_implicit((Expr *) info->indpred);
+ info->indpredExpand = make_ands_implicit((Expr *) info->indpredExpand);
}
/* Build targetlist using the completed indexprs data */
@@ -941,8 +955,8 @@ infer_arbiter_indexes(PlannerInfo *root)
attno - FirstLowInvalidHeapAttributeNumber);
}
- inferElems = RelationGetIndexExpressions(idxRel);
- inferIndexExprs = RelationGetIndexPredicate(idxRel);
+ inferElems = RelationGetIndexExpressionsExpand(idxRel);
+ inferIndexExprs = RelationGetIndexPredicateExpand(idxRel);
break;
}
}
@@ -1072,7 +1086,7 @@ infer_arbiter_indexes(PlannerInfo *root)
continue;
/* Expression attributes (if any) must match */
- idxExprs = RelationGetIndexExpressions(idxRel);
+ idxExprs = RelationGetIndexExpressionsExpand(idxRel);
if (idxExprs)
{
if (varno != 1)
@@ -1139,7 +1153,7 @@ infer_arbiter_indexes(PlannerInfo *root)
if (list_difference(idxExprs, inferElems) != NIL)
continue;
- predExprs = RelationGetIndexPredicate(idxRel);
+ predExprs = RelationGetIndexPredicateExpand(idxRel);
if (predExprs)
{
if (varno != 1)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d6efd070..01e6b5b1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -7431,6 +7431,7 @@ genericcostestimate(PlannerInfo *root,
double qual_arg_cost;
List *selectivityQuals;
ListCell *l;
+ bool needCalcIndexSelectivity = true;
/*
* If the index is partial, AND the index predicate with the explicitly
@@ -7495,9 +7496,23 @@ genericcostestimate(PlannerInfo *root,
* indexSelectivity estimate is tiny.
*/
if (numIndexTuples > index->tuples)
+ {
numIndexTuples = index->tuples;
+ needCalcIndexSelectivity = true;
+ }
+
+ /* Recalculate indexSelectivity based on the estimated numIndexTuples */
if (numIndexTuples < 1.0)
+ {
numIndexTuples = 1.0;
+ indexSelectivity = 0.0;
+ }
+ else if (needCalcIndexSelectivity && index->rel->tuples > 0)
+ {
+ Selectivity newIndexSelectivity = numIndexTuples / index->rel->tuples;
+ if (indexSelectivity > newIndexSelectivity)
+ indexSelectivity = newIndexSelectivity;
+ }
/*
* Estimate the number of index pages that will be retrieved.
@@ -7639,10 +7654,10 @@ add_predicate_to_index_quals(IndexOptInfo *index, List *indexQuals)
List *predExtraQuals = NIL;
ListCell *lc;
- if (index->indpred == NIL)
+ if (index->indpredExpand == NIL)
return indexQuals;
- foreach(lc, index->indpred)
+ foreach(lc, index->indpredExpand)
{
Node *predQual = (Node *) lfirst(lc);
List *oneQual = list_make1(predQual);
@@ -8054,6 +8069,12 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
JOIN_INNER,
NULL);
numIndexTuples = btreeSelectivity * index->rel->tuples;
+ /*
+ * The number of leaf tuples visited for cost
+ * cannot exceed the total tuples of the index.
+ */
+ if (numIndexTuples > index->tuples)
+ numIndexTuples = index->tuples;
/*
* btree automatically combines individual array element primitive
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0572ab42..e604bb91 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -91,6 +91,7 @@
#include "utils/resowner.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
+#include "rewrite/rewriteHandler.h"
#define RELCACHE_INIT_FILEMAGIC 0x573266 /* version ID value */
@@ -1583,7 +1584,9 @@ RelationInitIndexAccessInfo(Relation relation)
* expressions, predicate, exclusion caches will be filled later
*/
relation->rd_indexprs = NIL;
+ relation->rd_indexprsExpand = NIL;
relation->rd_indpred = NIL;
+ relation->rd_indpredExpand = NIL;
relation->rd_exclops = NULL;
relation->rd_exclprocs = NULL;
relation->rd_exclstrats = NULL;
@@ -5146,6 +5149,45 @@ RelationGetIndexExpressions(Relation relation)
return result;
}
+List *
+RelationGetIndexExpressionsExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indexprsExpand)
+ return copyObject(relation->rd_indexprsExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL))
+ return NIL;
+
+ result = RelationGetIndexExpressions(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indexprsExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetDummyIndexExpressions -- get dummy expressions for an index
*
@@ -5266,6 +5308,45 @@ RelationGetIndexPredicate(Relation relation)
return result;
}
+List *
+RelationGetIndexPredicateExpand(Relation relation)
+{
+ List *result;
+ MemoryContext oldcxt;
+ bool isnull;
+ Datum heapRelidDatum;
+ Oid heapRelid;
+
+ /* Quick exit if we already computed the result. */
+ if (relation->rd_indpredExpand)
+ return copyObject(relation->rd_indpredExpand);
+
+ /* Quick exit if there is nothing to do. */
+ if (relation->rd_indextuple == NULL ||
+ heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL))
+ return NIL;
+
+ result = RelationGetIndexPredicate(relation);
+ if (result == NIL)
+ return NIL;
+
+ heapRelidDatum = heap_getattr(relation->rd_indextuple,
+ Anum_pg_index_indrelid,
+ GetPgIndexDescriptor(),
+ &isnull);
+ Assert(!isnull);
+ heapRelid = DatumGetObjectId(heapRelidDatum);
+
+ result = ExpandVirtualGeneratedColumns(result, NULL, heapRelid);
+
+ /* Now save a copy of the completed tree in the relcache entry. */
+ oldcxt = MemoryContextSwitchTo(relation->rd_indexcxt);
+ relation->rd_indpredExpand = copyObject(result);
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
/*
* RelationGetIndexAttrBitmap -- get a bitmap of index attribute numbers
*
@@ -6490,7 +6571,9 @@ load_relcache_init_file(bool shared)
rel->rd_partcheckvalid = false;
rel->rd_partcheckcxt = NULL;
rel->rd_indexprs = NIL;
+ rel->rd_indexprsExpand = NIL;
rel->rd_indpred = NIL;
+ rel->rd_indpredExpand = NIL;
rel->rd_exclops = NULL;
rel->rd_exclprocs = NULL;
rel->rd_exclstrats = NULL;
@@ -7019,3 +7102,46 @@ ResOwnerReleaseRelation(Datum res)
RelationCloseCleanup((Relation) DatumGetPointer(res));
}
+
+List *
+ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId)
+{
+ bool opened_relation = false;
+ TupleDesc tupdesc;
+
+ if (list == NIL || (heapRelation == NULL && heapRelId == InvalidOid))
+ return list;
+
+ if (heapRelation == NULL)
+ {
+ heapRelation = table_open(heapRelId, NoLock);
+ opened_relation = true;
+ }
+
+ tupdesc = RelationGetDescr(heapRelation);
+ if ((tupdesc->constr && tupdesc->constr->has_generated_virtual))
+ {
+ int j;
+ Bitmapset *indexattrs = NULL;
+
+ pull_varattnos((Node *)list, 1, &indexattrs);
+
+ j = -1;
+ while ((j = bms_next_member(indexattrs, j)) >= 0)
+ {
+ AttrNumber attno = j + FirstLowInvalidHeapAttributeNumber;
+
+ if (attno > 0 &&
+ TupleDescAttr(tupdesc, attno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
+ {
+ list = (List *)expand_generated_columns_in_expr((Node *)list, heapRelation, 1);
+ break;
+ }
+ }
+ }
+
+ if (opened_relation)
+ table_close(heapRelation, NoLock);
+
+ return list;
+}
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 53c13831..20a2e618 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -190,13 +190,17 @@ typedef struct IndexInfo
/* expr trees for expression entries, or NIL if none */
List *ii_Expressions; /* list of Expr */
+ List *ii_ExpressionsExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
List *ii_ExpressionsState; /* list of ExprState */
+ List *ii_ExpressionsExpandState; /* list of ExprState */
/* partial-index predicate, or NIL if none */
List *ii_Predicate; /* list of Expr */
+ List *ii_PredicateExpand; /* list of Expr */
/* exec state for expressions, or NIL if none */
ExprState *ii_PredicateState;
+ ExprState *ii_PredicateExpandState;
/* Per-column exclusion operators, or NULL if none */
Oid *ii_ExclusionOps; /* array with one entry per column */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 27a2c681..9c65e123 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1403,8 +1403,10 @@ typedef struct IndexOptInfo
* print indextlist
*/
List *indexprs pg_node_attr(read_write_ignore);
+ List *indexprsExpand pg_node_attr(read_write_ignore);
/* predicate if a partial index, else NIL */
List *indpred;
+ List *indpredExpand;
/* targetlist representing index columns */
List *indextlist;
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index fa07ebf8..7bd01b36 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -210,7 +210,9 @@ typedef struct RelationData
struct FmgrInfo *rd_supportinfo; /* lookup info for support procedures */
int16 *rd_indoption; /* per-column AM-specific flags */
List *rd_indexprs; /* index expression trees, if any */
+ List *rd_indexprsExpand; /* expanded index expression trees, if any */
List *rd_indpred; /* index predicate tree, if any */
+ List *rd_indpredExpand; /* expanded index predicate tree, if any */
Oid *rd_exclops; /* OIDs of exclusion operators, if any */
Oid *rd_exclprocs; /* OIDs of exclusion ops' procs, if any */
uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 89c27aa1..70531bb8 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -58,8 +58,10 @@ extern List *RelationGetStatExtList(Relation relation);
extern Oid RelationGetPrimaryKeyIndex(Relation relation, bool deferrable_ok);
extern Oid RelationGetReplicaIndex(Relation relation);
extern List *RelationGetIndexExpressions(Relation relation);
+extern List *RelationGetIndexExpressionsExpand(Relation relation);
extern List *RelationGetDummyIndexExpressions(Relation relation);
extern List *RelationGetIndexPredicate(Relation relation);
+extern List *RelationGetIndexPredicateExpand(Relation relation);
extern bytea **RelationGetIndexAttOptions(Relation relation, bool copy);
/*
@@ -161,4 +163,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt;
/* should be used only by relcache.c and postinit.c */
extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
+extern List *ExpandVirtualGeneratedColumns(List *list, Relation heapRelation, Oid heapRelId);
+
#endif /* RELCACHE_H */
diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out
index 24d5dbf4..cd00a43d 100644
--- a/src/test/regress/expected/generated_virtual.out
+++ b/src/test/regress/expected/generated_virtual.out
@@ -370,6 +370,7 @@ Not-null constraints:
"gtest1_a_not_null" NOT NULL "a" (inherited)
Inherits:
gtest1
+Access method: heap
INSERT INTO gtestx (a, x) VALUES (11, 22);
SELECT * FROM gtest1;
@@ -763,39 +764,202 @@ ERROR: column "c" of relation "gtestnn_child" contains null values
ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok
-- index constraints
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE);
-ERROR: unique constraints on virtual generated columns are not supported
---INSERT INTO gtest22a VALUES (2);
---INSERT INTO gtest22a VALUES (3);
---INSERT INTO gtest22a VALUES (4);
+INSERT INTO gtest22a VALUES (2); -- ok
+INSERT INTO gtest22a VALUES (3); -- error
+ERROR: duplicate key value violates unique constraint "gtest22a_b_key"
+DETAIL: Key (b)=(1) already exists.
+INSERT INTO gtest22a VALUES (4); -- ok
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b));
-ERROR: primary keys on virtual generated columns are not supported
---INSERT INTO gtest22b VALUES (2);
---INSERT INTO gtest22b VALUES (2);
+INSERT INTO gtest22b VALUES (2); -- ok
+INSERT INTO gtest22b VALUES (2); -- error
+ERROR: duplicate key value violates unique constraint "gtest22b_pkey"
+DETAIL: Key (a, b)=(2, 1) already exists.
-- indexes
CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
---CREATE INDEX gtest22c_b_idx ON gtest22c (b);
---CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
---CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
---\d gtest22c
---INSERT INTO gtest22c VALUES (1), (2), (3);
---SET enable_seqscan TO off;
---SET enable_bitmapscan TO off;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
---SELECT * FROM gtest22c WHERE b = 4;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
---SELECT * FROM gtest22c WHERE b * 3 = 6;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
---ANALYZE gtest22c;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
---SELECT * FROM gtest22c WHERE b = 8;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
---SELECT * FROM gtest22c WHERE b * 3 = 12;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---RESET enable_seqscan;
---RESET enable_bitmapscan;
+CREATE INDEX gtest22c_b_idx ON gtest22c (b); -- ok
+CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -- ok
+CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -- ok
+\d gtest22c
+ Table "generated_virtual_tests.gtest22c"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+-----------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2)
+Indexes:
+ "gtest22c_b_idx" btree (b)
+ "gtest22c_expr_idx" btree ((b * 3))
+ "gtest22c_pred_idx" btree (a) WHERE b > 0
+
+INSERT INTO gtest22c VALUES (1), (2), (3);
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Filter: ((a * 2) = 4)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 4;
+ a | b
+---+---
+ 2 | 4
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
+ QUERY PLAN
+------------------------------------------------
+ Index Scan using gtest22c_expr_idx on gtest22c
+ Index Cond: (((a * 2) * 3) = 6)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b * 3 = 6;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Index Cond: (a = 1)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ a | b
+---+---
+ 1 | 2
+(1 row)
+
+ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
+ANALYZE gtest22c;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Filter: ((a * 4) = 8)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b = 8;
+ a | b
+---+---
+ 2 | 8
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
+ QUERY PLAN
+------------------------------------------------
+ Index Scan using gtest22c_expr_idx on gtest22c
+ Index Cond: (((a * 4) * 3) = 12)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE b * 3 = 12;
+ a | b
+---+---
+ 1 | 4
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ QUERY PLAN
+-----------------------------------------------------
+ Index Only Scan using gtest22c_pred_idx on gtest22c
+ Index Cond: (a = 1)
+(2 rows)
+
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+ a | b
+---+---
+ 1 | 4
+(1 row)
+
+CREATE TABLE gtest22d (a int
+ ,v1 int GENERATED ALWAYS AS (a * 11) VIRTUAL
+ ,v2 int GENERATED ALWAYS AS (a * 22) VIRTUAL
+ ,v3 int GENERATED ALWAYS AS (a * 33) VIRTUAL
+ ,v4 int GENERATED ALWAYS AS (a * 44) VIRTUAL
+); -- ok
+insert into gtest22d select oid from pg_catalog.pg_class union all select -1; -- ok
+CREATE INDEX gtest22d_vgc_idx ON gtest22d(a,v1,abs(v2)) Include(v3) WHERE v4 < 100; -- ok
+\d gtest22d
+ Table "generated_virtual_tests.gtest22d"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------
+ a | integer | | |
+ v1 | integer | | | generated always as (a * 11)
+ v2 | integer | | | generated always as (a * 22)
+ v3 | integer | | | generated always as (a * 33)
+ v4 | integer | | | generated always as (a * 44)
+Indexes:
+ "gtest22d_vgc_idx" btree (a, v1, abs(v2)) INCLUDE (v3) WHERE v4 < 100
+
+analyze gtest22d;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 < 100; --Index Only Scan or Index Scan
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using gtest22d_vgc_idx on gtest22d
+(1 row)
+
+SELECT * FROM gtest22d WHERE v4 < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 * 5 < 100; --Seq Scan
+ QUERY PLAN
+----------------------------------
+ Seq Scan on gtest22d
+ Disabled: true
+ Filter: (((a * 44) * 5) < 100)
+(3 rows)
+
+SELECT * FROM gtest22d WHERE v4 * 5 < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE a * 44 < 100; --Index Only Scan or Index Scan
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using gtest22d_vgc_idx on gtest22d
+(1 row)
+
+SELECT * FROM gtest22d WHERE a * 44 < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 44) < 100; --Index Only Scan or Index Scan
+ QUERY PLAN
+----------------------------------------------------
+ Index Only Scan using gtest22d_vgc_idx on gtest22d
+(1 row)
+
+SELECT * FROM gtest22d WHERE (a * 44) < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 33) < 100; --Seq Scan
+ QUERY PLAN
+----------------------------
+ Seq Scan on gtest22d
+ Disabled: true
+ Filter: ((a * 33) < 100)
+(3 rows)
+
+SELECT * FROM gtest22d WHERE (a * 33) < 100;
+ a | v1 | v2 | v3 | v4
+----+-----+-----+-----+-----
+ -1 | -11 | -22 | -33 | -44
+(1 row)
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
-- foreign keys
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
--INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33);
@@ -813,12 +977,12 @@ ERROR: foreign key constraints on virtual generated columns are not supported
--DROP TABLE gtest23b;
--DROP TABLE gtest23a;
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y));
-ERROR: primary keys on virtual generated columns are not supported
---INSERT INTO gtest23p VALUES (1), (2), (3);
+INSERT INTO gtest23p VALUES (1), (2), (3); -- ok
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
-ERROR: relation "gtest23p" does not exist
---INSERT INTO gtest23q VALUES (1, 2); -- ok
---INSERT INTO gtest23q VALUES (2, 5); -- error
+INSERT INTO gtest23q VALUES (1, 2); -- ok
+INSERT INTO gtest23q VALUES (2, 5); -- error
+ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey"
+DETAIL: Key (b)=(5) is not present in table "gtest23p".
-- domains
CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) VIRTUAL);
diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql
index 9c2bb659..5ebdc5ef 100644
--- a/src/test/regress/sql/generated_virtual.sql
+++ b/src/test/regress/sql/generated_virtual.sql
@@ -396,40 +396,62 @@ ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif
-- index constraints
CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE);
---INSERT INTO gtest22a VALUES (2);
---INSERT INTO gtest22a VALUES (3);
---INSERT INTO gtest22a VALUES (4);
+INSERT INTO gtest22a VALUES (2); -- ok
+INSERT INTO gtest22a VALUES (3); -- error
+INSERT INTO gtest22a VALUES (4); -- ok
CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) VIRTUAL, PRIMARY KEY (a, b));
---INSERT INTO gtest22b VALUES (2);
---INSERT INTO gtest22b VALUES (2);
+INSERT INTO gtest22b VALUES (2); -- ok
+INSERT INTO gtest22b VALUES (2); -- error
-- indexes
CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
---CREATE INDEX gtest22c_b_idx ON gtest22c (b);
---CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3));
---CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0;
---\d gtest22c
-
---INSERT INTO gtest22c VALUES (1), (2), (3);
---SET enable_seqscan TO off;
---SET enable_bitmapscan TO off;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
---SELECT * FROM gtest22c WHERE b = 4;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
---SELECT * FROM gtest22c WHERE b * 3 = 6;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
-
---ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
---ANALYZE gtest22c;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
---SELECT * FROM gtest22c WHERE b = 8;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
---SELECT * FROM gtest22c WHERE b * 3 = 12;
---EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
---RESET enable_seqscan;
---RESET enable_bitmapscan;
+CREATE INDEX gtest22c_b_idx ON gtest22c (b); -- ok
+CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -- ok
+CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -- ok
+\d gtest22c
+
+INSERT INTO gtest22c VALUES (1), (2), (3);
+SET enable_seqscan TO off;
+SET enable_bitmapscan TO off;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4;
+SELECT * FROM gtest22c WHERE b = 4;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6;
+SELECT * FROM gtest22c WHERE b * 3 = 6;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+
+ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4);
+ANALYZE gtest22c;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8;
+SELECT * FROM gtest22c WHERE b = 8;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12;
+SELECT * FROM gtest22c WHERE b * 3 = 12;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+SELECT * FROM gtest22c WHERE a = 1 AND b > 0;
+
+CREATE TABLE gtest22d (a int
+ ,v1 int GENERATED ALWAYS AS (a * 11) VIRTUAL
+ ,v2 int GENERATED ALWAYS AS (a * 22) VIRTUAL
+ ,v3 int GENERATED ALWAYS AS (a * 33) VIRTUAL
+ ,v4 int GENERATED ALWAYS AS (a * 44) VIRTUAL
+); -- ok
+insert into gtest22d select oid from pg_catalog.pg_class union all select -1; -- ok
+CREATE INDEX gtest22d_vgc_idx ON gtest22d(a,v1,abs(v2)) Include(v3) WHERE v4 < 100; -- ok
+\d gtest22d
+analyze gtest22d;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 < 100; --Index Only Scan or Index Scan
+SELECT * FROM gtest22d WHERE v4 < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE v4 * 5 < 100; --Seq Scan
+SELECT * FROM gtest22d WHERE v4 * 5 < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE a * 44 < 100; --Index Only Scan or Index Scan
+SELECT * FROM gtest22d WHERE a * 44 < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 44) < 100; --Index Only Scan or Index Scan
+SELECT * FROM gtest22d WHERE (a * 44) < 100;
+EXPLAIN (COSTS OFF) SELECT * FROM gtest22d WHERE (a * 33) < 100; --Seq Scan
+SELECT * FROM gtest22d WHERE (a * 33) < 100;
+
+RESET enable_seqscan;
+RESET enable_bitmapscan;
-- foreign keys
CREATE TABLE gtest23a (x int PRIMARY KEY, y int);
@@ -450,11 +472,11 @@ CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRT
--DROP TABLE gtest23a;
CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) VIRTUAL, PRIMARY KEY (y));
---INSERT INTO gtest23p VALUES (1), (2), (3);
+INSERT INTO gtest23p VALUES (1), (2), (3); -- ok
CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y));
---INSERT INTO gtest23q VALUES (1, 2); -- ok
---INSERT INTO gtest23q VALUES (2, 5); -- error
+INSERT INTO gtest23q VALUES (1, 2); -- ok
+INSERT INTO gtest23q VALUES (2, 5); -- error
-- domains
CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10);
--
2.43.0
[application/octet-stream] v2-相关成员、函数的整体分析.xlsx (47.5K, ../../[email protected]/3-v2-%E7%9B%B8%E5%85%B3%E6%88%90%E5%91%98%E3%80%81%E5%87%BD%E6%95%B0%E7%9A%84%E6%95%B4%E4%BD%93%E5%88%86%E6%9E%90.xlsx)
download
[application/octet-stream] v2-Comprehensive Analysis of Related Members and Functions.xlsx (47.4K, ../../[email protected]/4-v2-Comprehensive%20Analysis%20of%20Related%20Members%20and%20Functions.xlsx)
download
^ permalink raw reply [nested|flat] 10+ messages in thread
end of thread, other threads:[~2026-07-08 10:02 UTC | newest]
Thread overview: 10+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-02-19 20:33 [PATCH v1 3/6] Use dlist for syncrep queue. Andres Freund <[email protected]>
2022-11-19 23:18 [PATCH v2 4/9] Use dlist for syncrep queue Andres Freund <[email protected]>
2024-03-18 22:59 [PATCH v4 02/19] Remove unused PruneState member rel Melanie Plageman <[email protected]>
2026-01-08 06:16 Re: support create index on virtual generated column. jian he <[email protected]>
2026-02-27 10:45 ` Re: support create index on virtual generated column. Soumya S Murali <[email protected]>
2026-07-05 03:04 ` Re: support create index on virtual generated column. =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
2026-07-05 09:00 ` Re: support create index on virtual generated column. =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
2026-07-06 16:09 ` Re: support create index on virtual generated column. =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
2026-07-07 13:40 ` Re: support create index on virtual generated column. =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[email protected]>
2026-07-08 10:02 ` Re: support create index on virtual generated column. =?utf-8?B?Wml6aHVhbkxpdSBYLU1BTg==?= <[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