Skip to content

Commit d652536

Browse files
authored
Merge pull request IvorySQL#1000 from rophy/feat/rownum
feat: ROWNUM Implementation
2 parents 3588213 + e436367 commit d652536

41 files changed

Lines changed: 5202 additions & 13 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/backend/executor/execExpr.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,41 @@ ExecInitExprList(List *nodes, PlanState *parent)
377377
* Caution: before PG v10, the targetList was a list of ExprStates; now it
378378
* should be the planner-created targetlist, since we do the compilation here.
379379
*/
380+
381+
/*
382+
* Helper function to check if an expression contains ROWNUM
383+
*/
384+
static bool
385+
expression_contains_rownum_walker(Node *node, void *context)
386+
{
387+
if (node == NULL)
388+
return false;
389+
390+
if (IsA(node, RownumExpr))
391+
return true;
392+
393+
return expression_tree_walker(node, expression_contains_rownum_walker, context);
394+
}
395+
396+
/*
397+
* Check if a target list contains ROWNUM expressions
398+
*/
399+
static bool
400+
targetlist_contains_rownum(List *targetList)
401+
{
402+
ListCell *lc;
403+
404+
foreach(lc, targetList)
405+
{
406+
TargetEntry *tle = lfirst_node(TargetEntry, lc);
407+
408+
if (expression_contains_rownum_walker((Node *) tle->expr, NULL))
409+
return true;
410+
}
411+
412+
return false;
413+
}
414+
380415
ProjectionInfo *
381416
ExecBuildProjectionInfo(List *targetList,
382417
ExprContext *econtext,
@@ -519,6 +554,13 @@ ExecBuildProjectionInfo(List *targetList,
519554

520555
ExecReadyExpr(state);
521556

557+
/*
558+
* Check if the target list contains ROWNUM expressions.
559+
* If so, we need to materialize the result tuple to preserve the
560+
* ROWNUM values and prevent re-evaluation in outer queries.
561+
*/
562+
projInfo->pi_needsMaterialization = targetlist_contains_rownum(targetList);
563+
522564
return projInfo;
523565
}
524566

@@ -2646,6 +2688,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
26462688
break;
26472689
}
26482690

2691+
case T_RownumExpr:
2692+
{
2693+
/* Oracle ROWNUM pseudocolumn */
2694+
scratch.opcode = EEOP_ROWNUM;
2695+
ExprEvalPushStep(state, &scratch);
2696+
break;
2697+
}
2698+
26492699
case T_ReturningExpr:
26502700
{
26512701
ReturningExpr *rexpr = (ReturningExpr *) node;

src/backend/executor/execExprInterp.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
552552
&&CASE_EEOP_SQLVALUEFUNCTION,
553553
&&CASE_EEOP_CURRENTOFEXPR,
554554
&&CASE_EEOP_NEXTVALUEEXPR,
555+
&&CASE_EEOP_ROWNUM,
555556
&&CASE_EEOP_RETURNINGEXPR,
556557
&&CASE_EEOP_ARRAYEXPR,
557558
&&CASE_EEOP_ARRAYCOERCE,
@@ -1593,6 +1594,18 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
15931594
EEO_NEXT();
15941595
}
15951596

1597+
EEO_CASE(EEOP_ROWNUM)
1598+
{
1599+
/*
1600+
* Oracle ROWNUM pseudocolumn: return the current row number.
1601+
* The row number is incremented by the executor for each row
1602+
* emitted.
1603+
*/
1604+
ExecEvalRownum(state, op);
1605+
1606+
EEO_NEXT();
1607+
}
1608+
15961609
EEO_CASE(EEOP_RETURNINGEXPR)
15971610
{
15981611
/*
@@ -3322,6 +3335,41 @@ ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op)
33223335
*op->resnull = false;
33233336
}
33243337

3338+
/*
3339+
* Evaluate Oracle ROWNUM pseudocolumn.
3340+
*
3341+
* Returns the current row number from the executor state. The row number
3342+
* is incremented for each row emitted during query execution.
3343+
*
3344+
* ROWNUM starts at 1 and increments before any ORDER BY is applied.
3345+
*/
3346+
void
3347+
ExecEvalRownum(ExprState *state, ExprEvalStep *op)
3348+
{
3349+
PlanState *planstate;
3350+
EState *estate = NULL;
3351+
int64 rownum_value = 1; /* default */
3352+
3353+
/* Safely get the PlanState and EState */
3354+
if (state && state->parent)
3355+
{
3356+
planstate = state->parent;
3357+
if (planstate)
3358+
estate = planstate->state;
3359+
}
3360+
3361+
/*
3362+
* Use the estate-level ROWNUM counter.
3363+
* When ROWNUM appears in a SELECT list, materialization (handled in
3364+
* ExecScanExtended) ensures the value is captured and not re-evaluated.
3365+
*/
3366+
if (estate && estate->es_rownum > 0)
3367+
rownum_value = estate->es_rownum;
3368+
3369+
*op->resvalue = Int64GetDatum(rownum_value);
3370+
*op->resnull = false;
3371+
}
3372+
33253373
/*
33263374
* Evaluate NullTest / IS NULL for rows.
33273375
*/

src/backend/executor/execUtils.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ CreateExecutorState(void)
162162
estate->es_parallel_workers_to_launch = 0;
163163
estate->es_parallel_workers_launched = 0;
164164

165+
/* Oracle ROWNUM support: initialize row counter */
166+
estate->es_rownum = 0;
167+
165168
estate->es_jit_flags = 0;
166169
estate->es_jit = NULL;
167170

src/backend/executor/nodeAppend.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,9 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
290290
/* For parallel query, this will be overridden later. */
291291
appendstate->choose_next_subplan = choose_next_subplan_locally;
292292

293+
/* Copy is_union flag for ROWNUM reset handling (Oracle compatibility) */
294+
appendstate->as_is_union = node->is_union;
295+
293296
return appendstate;
294297
}
295298

@@ -386,6 +389,13 @@ ExecAppend(PlanState *pstate)
386389
/* choose new sync subplan; if no sync/async subplans, we're done */
387390
if (!node->choose_next_subplan(node) && node->as_nasyncremain == 0)
388391
return ExecClearTuple(node->ps.ps_ResultTupleSlot);
392+
393+
/*
394+
* For UNION queries, reset ROWNUM when switching to a new branch.
395+
* In Oracle, each UNION branch has its own independent ROWNUM counter.
396+
*/
397+
if (node->as_is_union)
398+
node->ps.state->es_rownum = 0;
389399
}
390400
}
391401

src/backend/executor/nodeMergeAppend.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
202202
*/
203203
mergestate->ms_initialized = false;
204204

205+
/* Copy is_union flag for ROWNUM reset handling (Oracle compatibility) */
206+
mergestate->ms_is_union = node->is_union;
207+
205208
return mergestate;
206209
}
207210

@@ -238,10 +241,22 @@ ExecMergeAppend(PlanState *pstate)
238241
/*
239242
* First time through: pull the first tuple from each valid subplan,
240243
* and set up the heap.
244+
*
245+
* For UNION queries, reset ROWNUM before each subplan starts.
246+
* This ensures each UNION branch has independent ROWNUM counting
247+
* (Oracle compatibility).
241248
*/
242249
i = -1;
243250
while ((i = bms_next_member(node->ms_valid_subplans, i)) >= 0)
244251
{
252+
/*
253+
* For UNION, reset ROWNUM before each branch executes.
254+
* Each child's Sort will buffer all tuples from its scan,
255+
* so ROWNUM needs to start fresh for each branch.
256+
*/
257+
if (node->ms_is_union)
258+
node->ps.state->es_rownum = 0;
259+
245260
node->ms_slots[i] = ExecProcNode(node->mergeplans[i]);
246261
if (!TupIsNull(node->ms_slots[i]))
247262
binaryheap_add_unordered(node->ms_heap, Int32GetDatum(i));

src/backend/executor/nodeResult.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,18 @@ ExecResult(PlanState *pstate)
132132
}
133133

134134
/* form the result tuple using ExecProject(), and return it */
135-
return ExecProject(node->ps.ps_ProjInfo);
135+
TupleTableSlot *result = ExecProject(node->ps.ps_ProjInfo);
136+
137+
/*
138+
* If the projection contains ROWNUM expressions, materialize
139+
* the virtual tuple to preserve the ROWNUM values as constants.
140+
*/
141+
if (node->ps.ps_ProjInfo && node->ps.ps_ProjInfo->pi_needsMaterialization)
142+
{
143+
ExecMaterializeSlot(result);
144+
}
145+
146+
return result;
136147
}
137148

138149
return NULL;

src/backend/executor/nodeSubplan.c

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ ExecSubPlan(SubPlanState *node,
6666
SubPlan *subplan = node->subplan;
6767
EState *estate = node->planstate->state;
6868
ScanDirection dir = estate->es_direction;
69+
int64 save_rownum = estate->es_rownum;
6970
Datum retval;
7071

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

86+
/*
87+
* Reset ROWNUM counter for Oracle compatibility.
88+
* Each correlated subquery invocation should start with ROWNUM=0,
89+
* matching Oracle's behavior.
90+
*/
91+
estate->es_rownum = 0;
92+
8593
/* Select appropriate evaluation strategy */
8694
if (subplan->useHashTable)
8795
retval = ExecHashSubPlan(node, econtext, isNull);
8896
else
8997
retval = ExecScanSubPlan(node, econtext, isNull);
9098

91-
/* restore scan direction */
99+
/* restore scan direction and ROWNUM counter */
92100
estate->es_direction = dir;
101+
estate->es_rownum = save_rownum;
93102

94103
return retval;
95104
}
@@ -262,6 +271,12 @@ ExecScanSubPlan(SubPlanState *node,
262271
/* with that done, we can reset the subplan */
263272
ExecReScan(planstate);
264273

274+
/*
275+
* Reset ROWNUM counter for Oracle compatibility.
276+
* This ensures correlated subqueries start fresh for each outer row.
277+
*/
278+
planstate->state->es_rownum = 0;
279+
265280
/*
266281
* For all sublink types except EXPR_SUBLINK and ARRAY_SUBLINK, the result
267282
* is boolean as are the results of the combining operators. We combine
@@ -1104,6 +1119,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
11041119
SubLinkType subLinkType = subplan->subLinkType;
11051120
EState *estate = planstate->state;
11061121
ScanDirection dir = estate->es_direction;
1122+
int64 save_rownum = estate->es_rownum;
11071123
MemoryContext oldcontext;
11081124
TupleTableSlot *slot;
11091125
ListCell *l;
@@ -1124,6 +1140,12 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
11241140
*/
11251141
estate->es_direction = ForwardScanDirection;
11261142

1143+
/*
1144+
* Reset ROWNUM counter for Oracle compatibility.
1145+
* InitPlans should start with ROWNUM=0, matching Oracle's behavior.
1146+
*/
1147+
estate->es_rownum = 0;
1148+
11271149
/* Initialize ArrayBuildStateAny in caller's context, if needed */
11281150
if (subLinkType == ARRAY_SUBLINK)
11291151
astate = initArrayResultAny(subplan->firstColType,
@@ -1257,8 +1279,9 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
12571279

12581280
MemoryContextSwitchTo(oldcontext);
12591281

1260-
/* restore scan direction */
1282+
/* restore scan direction and ROWNUM counter */
12611283
estate->es_direction = dir;
1284+
estate->es_rownum = save_rownum;
12621285
}
12631286

12641287
/*

src/backend/executor/nodeSubqueryscan.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,26 @@ static TupleTableSlot *
4646
SubqueryNext(SubqueryScanState *node)
4747
{
4848
TupleTableSlot *slot;
49+
EState *estate = node->ss.ps.state;
50+
int64 save_rownum = estate->es_rownum;
51+
52+
/*
53+
* For Oracle ROWNUM compatibility: each subquery maintains its own
54+
* local ROWNUM counter. Save the outer query's counter, swap in
55+
* this subquery's counter, execute the subplan, then restore.
56+
* This allows nested subqueries to have independent ROWNUM sequences.
57+
*/
58+
estate->es_rownum = node->sub_rownum;
4959

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

65+
/* Update local counter and restore outer query's counter */
66+
node->sub_rownum = estate->es_rownum;
67+
estate->es_rownum = save_rownum;
68+
5569
/*
5670
* We just return the subplan's result slot, rather than expending extra
5771
* cycles for ExecCopySlot(). (Our own ScanTupleSlot is used only for
@@ -112,6 +126,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
112126
subquerystate->ss.ps.plan = (Plan *) node;
113127
subquerystate->ss.ps.state = estate;
114128
subquerystate->ss.ps.ExecProcNode = ExecSubqueryScan;
129+
subquerystate->sub_rownum = 0;
115130

116131
/*
117132
* Miscellaneous initialization
@@ -182,6 +197,12 @@ ExecEndSubqueryScan(SubqueryScanState *node)
182197
void
183198
ExecReScanSubqueryScan(SubqueryScanState *node)
184199
{
200+
/*
201+
* Reset local ROWNUM counter for Oracle compatibility.
202+
* Each rescan starts with ROWNUM = 1.
203+
*/
204+
node->sub_rownum = 0;
205+
185206
ExecScanReScan(&node->ss);
186207

187208
/*

0 commit comments

Comments
 (0)