Skip to content

Commit c1ea1b5

Browse files
authored
feat: implement Oracle-compatible ROWNUM pseudocolumn
Add ROWNUM pseudocolumn with Oracle-compatible semantics: - ROWNUM <= N optimized to LIMIT for simple queries - Blocks LIMIT optimization for complex queries (ORDER BY, DISTINCT, GROUP BY, aggregation) to preserve Oracle semantics - Handles special Oracle edge cases: ROWNUM > N, ROWNUM >= N, ROWNUM = N - Supports tautology cases: ROWNUM > 0, ROWNUM >= 1 return all rows - Marks ROWNUM as volatile to ensure per-row evaluation Includes comprehensive regression tests covering all edge cases.
1 parent c51d958 commit c1ea1b5

File tree

20 files changed

+1599
-2
lines changed

20 files changed

+1599
-2
lines changed

src/backend/executor/execExpr.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2646,6 +2646,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
26462646
break;
26472647
}
26482648

2649+
case T_RownumExpr:
2650+
{
2651+
/* Oracle ROWNUM pseudocolumn */
2652+
scratch.opcode = EEOP_ROWNUM;
2653+
ExprEvalPushStep(state, &scratch);
2654+
break;
2655+
}
2656+
26492657
case T_ReturningExpr:
26502658
{
26512659
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+
3352+
/* Safely get the EState from the parent PlanState */
3353+
if (state && state->parent)
3354+
{
3355+
planstate = state->parent;
3356+
if (planstate)
3357+
estate = planstate->state;
3358+
}
3359+
3360+
/*
3361+
* Return current row number as INT8 (bigint).
3362+
* The counter starts at 1 and is incremented before each ExecProcNode call.
3363+
* For standalone expressions without an estate, default to 1.
3364+
*/
3365+
if (estate)
3366+
*op->resvalue = Int64GetDatum(estate->es_rownum);
3367+
else
3368+
*op->resvalue = Int64GetDatum(1);
3369+
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 to 0 */
166+
estate->es_rownum = 0;
167+
165168
estate->es_jit_flags = 0;
166169
estate->es_jit = NULL;
167170

src/backend/nodes/nodeFuncs.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ exprType(const Node *expr)
291291
case T_NextValueExpr:
292292
type = ((const NextValueExpr *) expr)->typeId;
293293
break;
294+
case T_RownumExpr:
295+
/* ROWNUM returns a numeric value */
296+
type = INT8OID;
297+
break;
294298
case T_InferenceElem:
295299
{
296300
const InferenceElem *n = (const InferenceElem *) expr;
@@ -1072,6 +1076,10 @@ exprCollation(const Node *expr)
10721076
/* NextValueExpr's result is an integer type ... */
10731077
coll = InvalidOid; /* ... so it has no collation */
10741078
break;
1079+
case T_RownumExpr:
1080+
/* RownumExpr's result is an integer type ... */
1081+
coll = InvalidOid; /* ... so it has no collation */
1082+
break;
10751083
case T_InferenceElem:
10761084
coll = exprCollation((Node *) ((const InferenceElem *) expr)->expr);
10771085
break;
@@ -1329,6 +1337,10 @@ exprSetCollation(Node *expr, Oid collation)
13291337
/* NextValueExpr's result is an integer type ... */
13301338
Assert(!OidIsValid(collation)); /* ... so never set a collation */
13311339
break;
1340+
case T_RownumExpr:
1341+
/* RownumExpr's result is an integer type ... */
1342+
Assert(!OidIsValid(collation)); /* ... so never set a collation */
1343+
break;
13321344
default:
13331345
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
13341346
break;
@@ -2164,6 +2176,7 @@ expression_tree_walker_impl(Node *node,
21642176
case T_SetToDefault:
21652177
case T_CurrentOfExpr:
21662178
case T_NextValueExpr:
2179+
case T_RownumExpr:
21672180
case T_RangeTblRef:
21682181
case T_SortGroupClause:
21692182
case T_CTESearchClause:
@@ -3045,6 +3058,7 @@ expression_tree_mutator_impl(Node *node,
30453058
case T_SetToDefault:
30463059
case T_CurrentOfExpr:
30473060
case T_NextValueExpr:
3061+
case T_RownumExpr:
30483062
case T_RangeTblRef:
30493063
case T_SortGroupClause:
30503064
case T_CTESearchClause:

0 commit comments

Comments
 (0)