|
22 | 22 | #include "executor/executor.h" |
23 | 23 | #include "executor/tstoreReceiver.h" |
24 | 24 | #include "miscadmin.h" |
| 25 | +#include "nodes/nodeFuncs.h" |
| 26 | +#include "nodes/plannodes.h" |
25 | 27 | #include "pg_trace.h" |
26 | 28 | #include "tcop/pquery.h" |
27 | 29 | #include "tcop/utility.h" |
@@ -60,6 +62,130 @@ static uint64 DoPortalRunFetch(Portal portal, |
60 | 62 | DestReceiver *dest); |
61 | 63 | static void DoPortalRewind(Portal portal); |
62 | 64 | static TupleDesc CreateTupleDescFromParams(ParamListInfo params); |
| 65 | +static bool plan_contains_rownum_walker(Node *node, void *context); |
| 66 | +static bool plan_contains_rownum(Plan *plan); |
| 67 | +static void FillPortalStoreForSelect(Portal portal); |
| 68 | + |
| 69 | +/* |
| 70 | + * Helper function to check if an expression contains ROWNUM |
| 71 | + */ |
| 72 | +static bool |
| 73 | +plan_contains_rownum_walker(Node *node, void *context) |
| 74 | +{ |
| 75 | + if (node == NULL) |
| 76 | + return false; |
| 77 | + |
| 78 | + if (IsA(node, RownumExpr)) |
| 79 | + return true; |
| 80 | + |
| 81 | + return expression_tree_walker(node, plan_contains_rownum_walker, context); |
| 82 | +} |
| 83 | + |
| 84 | +/* |
| 85 | + * Check if a plan tree contains ROWNUM expressions in targetlists. |
| 86 | + * This is used to determine if scroll cursor results need materialization. |
| 87 | + */ |
| 88 | +static bool |
| 89 | +plan_contains_rownum(Plan *plan) |
| 90 | +{ |
| 91 | + ListCell *lc; |
| 92 | + |
| 93 | + if (plan == NULL) |
| 94 | + return false; |
| 95 | + |
| 96 | + /* Check the plan's targetlist */ |
| 97 | + foreach(lc, plan->targetlist) |
| 98 | + { |
| 99 | + TargetEntry *tle = lfirst_node(TargetEntry, lc); |
| 100 | + |
| 101 | + if (plan_contains_rownum_walker((Node *) tle->expr, NULL)) |
| 102 | + return true; |
| 103 | + } |
| 104 | + |
| 105 | + /* Recursively check standard child plans */ |
| 106 | + if (plan->lefttree && plan_contains_rownum(plan->lefttree)) |
| 107 | + return true; |
| 108 | + if (plan->righttree && plan_contains_rownum(plan->righttree)) |
| 109 | + return true; |
| 110 | + |
| 111 | + /* Handle special plan node types with additional child plans */ |
| 112 | + switch (nodeTag(plan)) |
| 113 | + { |
| 114 | + case T_SubqueryScan: |
| 115 | + { |
| 116 | + SubqueryScan *splan = (SubqueryScan *) plan; |
| 117 | + if (plan_contains_rownum(splan->subplan)) |
| 118 | + return true; |
| 119 | + } |
| 120 | + break; |
| 121 | + |
| 122 | + case T_Append: |
| 123 | + { |
| 124 | + Append *aplan = (Append *) plan; |
| 125 | + foreach(lc, aplan->appendplans) |
| 126 | + { |
| 127 | + if (plan_contains_rownum((Plan *) lfirst(lc))) |
| 128 | + return true; |
| 129 | + } |
| 130 | + } |
| 131 | + break; |
| 132 | + |
| 133 | + case T_MergeAppend: |
| 134 | + { |
| 135 | + MergeAppend *mplan = (MergeAppend *) plan; |
| 136 | + foreach(lc, mplan->mergeplans) |
| 137 | + { |
| 138 | + if (plan_contains_rownum((Plan *) lfirst(lc))) |
| 139 | + return true; |
| 140 | + } |
| 141 | + } |
| 142 | + break; |
| 143 | + |
| 144 | + default: |
| 145 | + /* Most plan nodes only have lefttree/righttree children */ |
| 146 | + break; |
| 147 | + } |
| 148 | + |
| 149 | + return false; |
| 150 | +} |
| 151 | + |
| 152 | +/* |
| 153 | + * FillPortalStoreForSelect |
| 154 | + * Run a PORTAL_ONE_SELECT query and store results in portal's holdStore. |
| 155 | + * |
| 156 | + * This is used for scroll cursors that contain ROWNUM expressions. |
| 157 | + * We need to materialize results so that FETCH PRIOR/NEXT don't re-evaluate |
| 158 | + * ROWNUM values. |
| 159 | + */ |
| 160 | +static void |
| 161 | +FillPortalStoreForSelect(Portal portal) |
| 162 | +{ |
| 163 | + QueryDesc *queryDesc = portal->queryDesc; |
| 164 | + DestReceiver *treceiver; |
| 165 | + |
| 166 | + Assert(queryDesc != NULL); |
| 167 | + Assert(portal->holdStore == NULL); |
| 168 | + |
| 169 | + PortalCreateHoldStore(portal); |
| 170 | + treceiver = CreateDestReceiver(DestTuplestore); |
| 171 | + SetTuplestoreDestReceiverParams(treceiver, |
| 172 | + portal->holdStore, |
| 173 | + portal->holdContext, |
| 174 | + false, |
| 175 | + NULL, |
| 176 | + NULL); |
| 177 | + |
| 178 | + /* Run the query to completion, storing results in tuplestore */ |
| 179 | + PushActiveSnapshot(queryDesc->snapshot); |
| 180 | + queryDesc->dest = treceiver; |
| 181 | + ExecutorRun(queryDesc, ForwardScanDirection, 0); |
| 182 | + PopActiveSnapshot(); |
| 183 | + |
| 184 | + treceiver->rDestroy(treceiver); |
| 185 | + |
| 186 | + /* Reset the tuplestore to start */ |
| 187 | + tuplestore_rescan(portal->holdStore); |
| 188 | +} |
63 | 189 |
|
64 | 190 | /* |
65 | 191 | * CreateQueryDesc |
@@ -892,6 +1018,21 @@ PortalRunSelect(Portal portal, |
892 | 1018 | /* Caller messed up if we have neither a ready query nor held data. */ |
893 | 1019 | Assert(queryDesc || portal->holdStore); |
894 | 1020 |
|
| 1021 | + /* |
| 1022 | + * For scroll cursors with ROWNUM in the plan, we need to materialize |
| 1023 | + * all results on first execution. This ensures that FETCH PRIOR/NEXT |
| 1024 | + * return the same ROWNUM values that were computed during the initial |
| 1025 | + * forward scan, rather than re-evaluating ROWNUM (which would give |
| 1026 | + * incorrect incrementing values). |
| 1027 | + */ |
| 1028 | + if (queryDesc != NULL && |
| 1029 | + portal->holdStore == NULL && |
| 1030 | + (portal->cursorOptions & CURSOR_OPT_SCROLL) && |
| 1031 | + plan_contains_rownum(queryDesc->plannedstmt->planTree)) |
| 1032 | + { |
| 1033 | + FillPortalStoreForSelect(portal); |
| 1034 | + } |
| 1035 | + |
895 | 1036 | /* |
896 | 1037 | * Force the queryDesc destination to the right thing. This supports |
897 | 1038 | * MOVE, for example, which will pass in dest = DestNone. This is okay to |
|
0 commit comments