Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/backend/executor/execExpr.c
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,41 @@ ExecInitExprList(List *nodes, PlanState *parent)
* Caution: before PG v10, the targetList was a list of ExprStates; now it
* should be the planner-created targetlist, since we do the compilation here.
*/

/*
* Helper function to check if an expression contains ROWNUM
*/
static bool
expression_contains_rownum_walker(Node *node, void *context)
{
if (node == NULL)
return false;

if (IsA(node, RownumExpr))
return true;

return expression_tree_walker(node, expression_contains_rownum_walker, context);
}

/*
* Check if a target list contains ROWNUM expressions
*/
static bool
targetlist_contains_rownum(List *targetList)
{
ListCell *lc;

foreach(lc, targetList)
{
TargetEntry *tle = lfirst_node(TargetEntry, lc);

if (expression_contains_rownum_walker((Node *) tle->expr, NULL))
return true;
}

return false;
}

ProjectionInfo *
ExecBuildProjectionInfo(List *targetList,
ExprContext *econtext,
Expand Down Expand Up @@ -519,6 +554,13 @@ ExecBuildProjectionInfo(List *targetList,

ExecReadyExpr(state);

/*
* Check if the target list contains ROWNUM expressions.
* If so, we need to materialize the result tuple to preserve the
* ROWNUM values and prevent re-evaluation in outer queries.
*/
projInfo->pi_needsMaterialization = targetlist_contains_rownum(targetList);

return projInfo;
}

Expand Down Expand Up @@ -2646,6 +2688,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
break;
}

case T_RownumExpr:
{
/* Oracle ROWNUM pseudocolumn */
scratch.opcode = EEOP_ROWNUM;
ExprEvalPushStep(state, &scratch);
break;
}

case T_ReturningExpr:
{
ReturningExpr *rexpr = (ReturningExpr *) node;
Expand Down
48 changes: 48 additions & 0 deletions src/backend/executor/execExprInterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_SQLVALUEFUNCTION,
&&CASE_EEOP_CURRENTOFEXPR,
&&CASE_EEOP_NEXTVALUEEXPR,
&&CASE_EEOP_ROWNUM,
&&CASE_EEOP_RETURNINGEXPR,
&&CASE_EEOP_ARRAYEXPR,
&&CASE_EEOP_ARRAYCOERCE,
Expand Down Expand Up @@ -1593,6 +1594,18 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}

EEO_CASE(EEOP_ROWNUM)
{
/*
* Oracle ROWNUM pseudocolumn: return the current row number.
* The row number is incremented by the executor for each row
* emitted.
*/
ExecEvalRownum(state, op);

EEO_NEXT();
}

EEO_CASE(EEOP_RETURNINGEXPR)
{
/*
Expand Down Expand Up @@ -3322,6 +3335,41 @@ ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op)
*op->resnull = false;
}

/*
* Evaluate Oracle ROWNUM pseudocolumn.
*
* Returns the current row number from the executor state. The row number
* is incremented for each row emitted during query execution.
*
* ROWNUM starts at 1 and increments before any ORDER BY is applied.
*/
void
ExecEvalRownum(ExprState *state, ExprEvalStep *op)
{
PlanState *planstate;
EState *estate = NULL;
int64 rownum_value = 1; /* default */

/* Safely get the PlanState and EState */
if (state && state->parent)
{
planstate = state->parent;
if (planstate)
estate = planstate->state;
}

/*
* Use the estate-level ROWNUM counter.
* When ROWNUM appears in a SELECT list, materialization (handled in
* ExecScanExtended) ensures the value is captured and not re-evaluated.
*/
if (estate && estate->es_rownum > 0)
rownum_value = estate->es_rownum;

*op->resvalue = Int64GetDatum(rownum_value);
*op->resnull = false;
}

/*
* Evaluate NullTest / IS NULL for rows.
*/
Expand Down
3 changes: 3 additions & 0 deletions src/backend/executor/execUtils.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ CreateExecutorState(void)
estate->es_parallel_workers_to_launch = 0;
estate->es_parallel_workers_launched = 0;

/* Oracle ROWNUM support: initialize row counter */
estate->es_rownum = 0;

estate->es_jit_flags = 0;
estate->es_jit = NULL;

Expand Down
10 changes: 10 additions & 0 deletions src/backend/executor/nodeAppend.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
/* For parallel query, this will be overridden later. */
appendstate->choose_next_subplan = choose_next_subplan_locally;

/* Copy is_union flag for ROWNUM reset handling (Oracle compatibility) */
appendstate->as_is_union = node->is_union;

return appendstate;
}

Expand Down Expand Up @@ -386,6 +389,13 @@ ExecAppend(PlanState *pstate)
/* choose new sync subplan; if no sync/async subplans, we're done */
if (!node->choose_next_subplan(node) && node->as_nasyncremain == 0)
return ExecClearTuple(node->ps.ps_ResultTupleSlot);

/*
* For UNION queries, reset ROWNUM when switching to a new branch.
* In Oracle, each UNION branch has its own independent ROWNUM counter.
*/
if (node->as_is_union)
node->ps.state->es_rownum = 0;
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/backend/executor/nodeMergeAppend.c
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
*/
mergestate->ms_initialized = false;

/* Copy is_union flag for ROWNUM reset handling (Oracle compatibility) */
mergestate->ms_is_union = node->is_union;

return mergestate;
}

Expand Down Expand Up @@ -238,10 +241,22 @@ ExecMergeAppend(PlanState *pstate)
/*
* First time through: pull the first tuple from each valid subplan,
* and set up the heap.
*
* For UNION queries, reset ROWNUM before each subplan starts.
* This ensures each UNION branch has independent ROWNUM counting
* (Oracle compatibility).
*/
i = -1;
while ((i = bms_next_member(node->ms_valid_subplans, i)) >= 0)
{
/*
* For UNION, reset ROWNUM before each branch executes.
* Each child's Sort will buffer all tuples from its scan,
* so ROWNUM needs to start fresh for each branch.
*/
if (node->ms_is_union)
node->ps.state->es_rownum = 0;

node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
if (!TupIsNull(node->ms_slots[i]))
binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i));
Expand Down
13 changes: 12 additions & 1 deletion src/backend/executor/nodeResult.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,18 @@ ExecResult(PlanState *pstate)
}

/* form the result tuple using ExecProject(), and return it */
return ExecProject(node->ps.ps_ProjInfo);
TupleTableSlot *result = ExecProject(node->ps.ps_ProjInfo);

/*
* If the projection contains ROWNUM expressions, materialize
* the virtual tuple to preserve the ROWNUM values as constants.
*/
if (node->ps.ps_ProjInfo && node->ps.ps_ProjInfo->pi_needsMaterialization)
{
ExecMaterializeSlot(result);
}

return result;
}

return NULL;
Expand Down
27 changes: 25 additions & 2 deletions src/backend/executor/nodeSubplan.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ ExecSubPlan(SubPlanState *node,
SubPlan *subplan = node->subplan;
EState *estate = node->planstate->state;
ScanDirection dir = estate->es_direction;
int64 save_rownum = estate->es_rownum;
Datum retval;

CHECK_FOR_INTERRUPTS();
Expand All @@ -82,14 +83,22 @@ ExecSubPlan(SubPlanState *node,
/* Force forward-scan mode for evaluation */
estate->es_direction = ForwardScanDirection;

/*
* Reset ROWNUM counter for Oracle compatibility.
* Each correlated subquery invocation should start with ROWNUM=0,
* matching Oracle's behavior.
*/
estate->es_rownum = 0;

/* Select appropriate evaluation strategy */
if (subplan->useHashTable)
retval = ExecHashSubPlan(node, econtext, isNull);
else
retval = ExecScanSubPlan(node, econtext, isNull);

/* restore scan direction */
/* restore scan direction and ROWNUM counter */
estate->es_direction = dir;
estate->es_rownum = save_rownum;

return retval;
}
Expand Down Expand Up @@ -262,6 +271,12 @@ ExecScanSubPlan(SubPlanState *node,
/* with that done, we can reset the subplan */
ExecReScan(planstate);

/*
* Reset ROWNUM counter for Oracle compatibility.
* This ensures correlated subqueries start fresh for each outer row.
*/
planstate->state->es_rownum = 0;

/*
* For all sublink types except EXPR_SUBLINK and ARRAY_SUBLINK, the result
* is boolean as are the results of the combining operators. We combine
Expand Down Expand Up @@ -1104,6 +1119,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
SubLinkType subLinkType = subplan->subLinkType;
EState *estate = planstate->state;
ScanDirection dir = estate->es_direction;
int64 save_rownum = estate->es_rownum;
MemoryContext oldcontext;
TupleTableSlot *slot;
ListCell *l;
Expand All @@ -1124,6 +1140,12 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
*/
estate->es_direction = ForwardScanDirection;

/*
* Reset ROWNUM counter for Oracle compatibility.
* InitPlans should start with ROWNUM=0, matching Oracle's behavior.
*/
estate->es_rownum = 0;

/* Initialize ArrayBuildStateAny in caller's context, if needed */
if (subLinkType == ARRAY_SUBLINK)
astate = initArrayResultAny(subplan->firstColType,
Expand Down Expand Up @@ -1257,8 +1279,9 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)

MemoryContextSwitchTo(oldcontext);

/* restore scan direction */
/* restore scan direction and ROWNUM counter */
estate->es_direction = dir;
estate->es_rownum = save_rownum;
}

/*
Expand Down
25 changes: 25 additions & 0 deletions src/backend/executor/nodeSubqueryscan.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,30 @@ static TupleTableSlot *
SubqueryNext(SubqueryScanState *node)
{
TupleTableSlot *slot;
bool first_call = !node->rownum_reset;

if (first_call)
node->rownum_reset = true;

/*
* Get the next tuple from the sub-query.
*/
slot = ExecProcNode(node->subplan);

/*
* For Oracle ROWNUM compatibility: reset the ROWNUM counter after
* the first call to ExecProcNode. This is necessary because inner
* plan nodes (e.g., SeqScan feeding a Sort) may increment es_rownum
* while buffering tuples during the first call. We want the
* SubqueryScan's ROWNUM values to start at 1, not continue from
* where the inner scans left off.
*
* The reset happens after ExecProcNode because blocking operators
* like Sort fetch all input tuples on the first call.
*/
if (first_call)
node->ss.ps.state->es_rownum = 0;

/*
* We just return the subplan's result slot, rather than expending extra
* cycles for ExecCopySlot(). (Our own ScanTupleSlot is used only for
Expand Down Expand Up @@ -112,6 +130,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
subquerystate->ss.ps.plan = (Plan *) node;
subquerystate->ss.ps.state = estate;
subquerystate->ss.ps.ExecProcNode = ExecSubqueryScan;
subquerystate->rownum_reset = false;

/*
* Miscellaneous initialization
Expand Down Expand Up @@ -182,6 +201,12 @@ ExecEndSubqueryScan(SubqueryScanState *node)
void
ExecReScanSubqueryScan(SubqueryScanState *node)
{
/*
* Reset ROWNUM tracking flag for Oracle compatibility.
* This ensures each SubqueryScan rescan resets ROWNUM on first tuple.
*/
node->rownum_reset = false;

ExecScanReScan(&node->ss);

/*
Expand Down
Loading