*** a/doc/src/sgml/ref/delete.sgml --- b/doc/src/sgml/ref/delete.sgml *************** *** 25,30 **** PostgreSQL documentation --- 25,31 ---- DELETE FROM [ ONLY ] table_name [ * ] [ [ AS ] alias ] [ USING using_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] + [ LIMIT { count | ALL } ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] *************** *** 56,61 **** DELETE FROM [ ONLY ] table_name [ * --- 57,70 ---- + If the LIMIT (or FETCH FIRST) clause + is present, processing will stop after the system has deleted the + specified amount of rows. Unlike in SELECT, the + OFFSET clause is not available in + DELETE. + + + The optional RETURNING clause causes DELETE to compute and return value(s) based on each row actually deleted. Any expression using the table's columns, and/or columns of other *** a/doc/src/sgml/ref/update.sgml --- b/doc/src/sgml/ref/update.sgml *************** *** 29,34 **** UPDATE [ ONLY ] table_name [ * ] [ --- 29,35 ---- } [, ...] [ FROM from_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] + [ LIMIT { count | ALL } ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] *************** *** 51,56 **** UPDATE [ ONLY ] table_name [ * ] [ --- 52,66 ---- circumstances. + + + If the LIMIT (or FETCH FIRST) clause + is present, processing will stop after the system has updated + the specified amount of rows. Unlike in SELECT, the + OFFSET clause is not available in + UPDATE. + + The optional RETURNING clause causes UPDATE to compute and return value(s) based on each row actually updated. *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 323,329 **** ExecDelete(ItemPointer tupleid, TupleTableSlot *planSlot, EPQState *epqstate, EState *estate, ! bool canSetTag) { ResultRelInfo *resultRelInfo; Relation resultRelationDesc; --- 323,329 ---- TupleTableSlot *planSlot, EPQState *epqstate, EState *estate, ! int64_t *processed) { ResultRelInfo *resultRelInfo; Relation resultRelationDesc; *************** *** 480,487 **** ldelete:; */ } ! if (canSetTag) ! (estate->es_processed)++; /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple); --- 480,486 ---- */ } ! (*processed)++; /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple); *************** *** 572,578 **** ExecUpdate(ItemPointer tupleid, TupleTableSlot *planSlot, EPQState *epqstate, EState *estate, ! bool canSetTag) { HeapTuple tuple; ResultRelInfo *resultRelInfo; --- 571,577 ---- TupleTableSlot *planSlot, EPQState *epqstate, EState *estate, ! int64_t *processed) { HeapTuple tuple; ResultRelInfo *resultRelInfo; *************** *** 771,778 **** lreplace:; estate); } ! if (canSetTag) ! (estate->es_processed)++; /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple, --- 770,776 ---- estate); } ! (*processed)++; /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple, *************** *** 885,896 **** ExecModifyTable(ModifyTableState *node) return NULL; /* ! * On first call, fire BEFORE STATEMENT triggers before proceeding. */ ! if (node->fireBSTriggers) { fireBSTriggers(node); ! node->fireBSTriggers = false; } /* Preload local variables */ --- 883,919 ---- return NULL; /* ! * On first call, evaluate the LIMIT expression if there is one, and fire ! * BEFORE STATEMENT triggers before proceeding. */ ! if (node->firstCall) { + if (node->limitCount) + { + ExprContext *econtext = node->ps.ps_ExprContext; + Datum val; + bool isNull; + + val = ExecEvalExprSwitchContext(node->limitCount, + econtext, + &isNull, + NULL); + if (isNull) + node->maxProcessed = -1; + else + { + node->maxProcessed = DatumGetInt64(val); + if (node->maxProcessed < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE), + errmsg("LIMIT must not be negative"))); + } + } + else + node->maxProcessed = -1; + fireBSTriggers(node); ! node->firstCall = false; } /* Preload local variables */ *************** *** 916,921 **** ExecModifyTable(ModifyTableState *node) --- 939,955 ---- for (;;) { /* + * Check that we haven't hit the LIMIT yet, if one was specified. We + * must do this inside the loop in case a RETURNING clause was not + * present. + */ + if (node->processed == node->maxProcessed) + { + node->mt_done = true; + break; + } + + /* * Reset the per-output-tuple exprcontext. This is needed because * triggers expect to use that context as workspace. It's a bit ugly * to do this below the top level of the plan, however. We might need *************** *** 1023,1036 **** ExecModifyTable(ModifyTableState *node) { case CMD_INSERT: slot = ExecInsert(slot, planSlot, estate, node->canSetTag); break; case CMD_UPDATE: slot = ExecUpdate(tupleid, oldtuple, slot, planSlot, ! &node->mt_epqstate, estate, node->canSetTag); break; case CMD_DELETE: slot = ExecDelete(tupleid, oldtuple, planSlot, ! &node->mt_epqstate, estate, node->canSetTag); break; default: elog(ERROR, "unknown operation"); --- 1057,1077 ---- { case CMD_INSERT: slot = ExecInsert(slot, planSlot, estate, node->canSetTag); + /* estate->es_processed already updated */ break; case CMD_UPDATE: slot = ExecUpdate(tupleid, oldtuple, slot, planSlot, ! &node->mt_epqstate, estate, &node->processed); ! /* keep EState up to date */ ! if (node->canSetTag) ! estate->es_processed = node->processed; break; case CMD_DELETE: slot = ExecDelete(tupleid, oldtuple, planSlot, ! &node->mt_epqstate, estate, &node->processed); ! /* keep EState up to date */ ! if (node->canSetTag) ! estate->es_processed = node->processed; break; default: elog(ERROR, "unknown operation"); *************** *** 1091,1096 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1132,1138 ---- mtstate->operation = operation; mtstate->canSetTag = node->canSetTag; + mtstate->processed = 0; mtstate->mt_done = false; mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans); *************** *** 1100,1106 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* set up epqstate with dummy subplan data for the moment */ EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam); ! mtstate->fireBSTriggers = true; /* * call ExecInitNode on each of the plans to be executed and save the --- 1142,1148 ---- /* set up epqstate with dummy subplan data for the moment */ EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam); ! mtstate->firstCall = true; /* * call ExecInitNode on each of the plans to be executed and save the *************** *** 1381,1386 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) --- 1423,1441 ---- estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate); /* + * If we had a LIMIT, initialize it. We will evaluate it before the first + * row is processed. + */ + if (node->limitCount) + { + /* also create a context if RETURNING didn't already do that */ + if (!mtstate->ps.ps_ExprContext) + mtstate->ps.ps_ExprContext = CreateExprContext(estate); + mtstate->limitCount = ExecInitExpr((Expr *) node->limitCount, + (PlanState *) mtstate); + } + + /* * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it * to estate->es_auxmodifytables so that it will be run to completion by * ExecPostprocessPlan. (It'd actually work fine to add the primary *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 183,188 **** _copyModifyTable(const ModifyTable *from) --- 183,189 ---- COPY_NODE_FIELD(fdwPrivLists); COPY_NODE_FIELD(rowMarks); COPY_SCALAR_FIELD(epqParam); + COPY_NODE_FIELD(limitCount); return newnode; } *************** *** 2532,2537 **** _copyDeleteStmt(const DeleteStmt *from) --- 2533,2539 ---- COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); + COPY_NODE_FIELD(limitClause); return newnode; } *************** *** 2547,2552 **** _copyUpdateStmt(const UpdateStmt *from) --- 2549,2555 ---- COPY_NODE_FIELD(fromClause); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); + COPY_NODE_FIELD(limitClause); return newnode; } *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 897,902 **** _equalDeleteStmt(const DeleteStmt *a, const DeleteStmt *b) --- 897,903 ---- COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); + COMPARE_NODE_FIELD(limitClause); return true; } *************** *** 910,915 **** _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b) --- 911,917 ---- COMPARE_NODE_FIELD(fromClause); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); + COMPARE_NODE_FIELD(limitClause); return true; } *** a/src/backend/nodes/nodeFuncs.c --- b/src/backend/nodes/nodeFuncs.c *************** *** 2988,2993 **** raw_expression_tree_walker(Node *node, --- 2988,2995 ---- return true; if (walker(stmt->withClause, context)) return true; + if (walker(stmt->limitClause, context)) + return true; } break; case T_UpdateStmt: *************** *** 3006,3011 **** raw_expression_tree_walker(Node *node, --- 3008,3015 ---- return true; if (walker(stmt->withClause, context)) return true; + if (walker(stmt->limitClause, context)) + return true; } break; case T_SelectStmt: *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** *** 337,342 **** _outModifyTable(StringInfo str, const ModifyTable *node) --- 337,343 ---- WRITE_NODE_FIELD(fdwPrivLists); WRITE_NODE_FIELD(rowMarks); WRITE_INT_FIELD(epqParam); + WRITE_NODE_FIELD(limitCount); } static void *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 4719,4725 **** make_modifytable(PlannerInfo *root, CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *withCheckOptionLists, List *returningLists, ! List *rowMarks, int epqParam) { ModifyTable *node = makeNode(ModifyTable); Plan *plan = &node->plan; --- 4719,4725 ---- CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *withCheckOptionLists, List *returningLists, ! List *rowMarks, int epqParam, Node *limitCount) { ModifyTable *node = makeNode(ModifyTable); Plan *plan = &node->plan; *************** *** 4772,4777 **** make_modifytable(PlannerInfo *root, --- 4772,4778 ---- node->returningLists = returningLists; node->rowMarks = rowMarks; node->epqParam = epqParam; + node->limitCount = limitCount; /* * For each result relation that is a foreign table, allow the FDW to *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** *** 610,616 **** subquery_planner(PlannerGlobal *glob, Query *parse, withCheckOptionLists, returningLists, rowMarks, ! SS_assign_special_param(root)); } } --- 610,617 ---- withCheckOptionLists, returningLists, rowMarks, ! SS_assign_special_param(root), ! parse->limitCount); } } *************** *** 1054,1060 **** inheritance_planner(PlannerInfo *root) withCheckOptionLists, returningLists, rowMarks, ! SS_assign_special_param(root)); } /*-------------------- --- 1055,1062 ---- withCheckOptionLists, returningLists, rowMarks, ! SS_assign_special_param(root), ! parse->limitCount); } /*-------------------- *************** *** 2473,2478 **** limit_needed(Query *parse) --- 2475,2485 ---- { Node *node; + /* ModifyTable handles the LIMIT for us if this is an UPDATE or a DELETE */ + if (parse->commandType == CMD_UPDATE || + parse->commandType == CMD_DELETE) + return false; + node = parse->limitCount; if (node) { *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 386,391 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) --- 386,394 ---- qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); + qry->limitCount = transformLimitClause(pstate, stmt->limitClause, + EXPR_KIND_LIMIT, "LIMIT"); + qry->returningList = transformReturningList(pstate, stmt->returningList); /* done building the range table and jointree */ *************** *** 1947,1952 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) --- 1950,1958 ---- qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); + qry->limitCount = transformLimitClause(pstate, stmt->limitClause, + EXPR_KIND_LIMIT, "LIMIT"); + qry->returningList = transformReturningList(pstate, stmt->returningList); qry->rtable = pstate->p_rtable; *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 9163,9175 **** returning_clause: *****************************************************************************/ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias ! using_clause where_or_current_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); n->relation = $4; n->usingClause = $5; n->whereClause = $6; ! n->returningList = $7; n->withClause = $1; $$ = (Node *)n; } --- 9163,9181 ---- *****************************************************************************/ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias ! using_clause where_or_current_clause opt_select_limit returning_clause { DeleteStmt *n = makeNode(DeleteStmt); n->relation = $4; n->usingClause = $5; n->whereClause = $6; ! if (linitial($7) != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("OFFSET is not supported with DELETE"), ! parser_errposition(@7))); ! n->limitClause = lsecond($7); ! n->returningList = $8; n->withClause = $1; $$ = (Node *)n; } *************** *** 9229,9234 **** UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias --- 9235,9241 ---- SET set_clause_list from_clause where_or_current_clause + opt_select_limit returning_clause { UpdateStmt *n = makeNode(UpdateStmt); *************** *** 9236,9242 **** UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias n->targetList = $5; n->fromClause = $6; n->whereClause = $7; ! n->returningList = $8; n->withClause = $1; $$ = (Node *)n; } --- 9243,9255 ---- n->targetList = $5; n->fromClause = $6; n->whereClause = $7; ! if (linitial($8) != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("OFFSET is not supported with UPDATE"), ! parser_errposition(@8))); ! n->limitClause = lsecond($8); ! n->returningList = $9; n->withClause = $1; $$ = (Node *)n; } *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** *** 1088,1094 **** typedef struct ModifyTableState ResultRelInfo *resultRelInfo; /* per-subplan target relations */ List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */ EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */ ! bool fireBSTriggers; /* do we need to fire stmt triggers? */ } ModifyTableState; /* ---------------- --- 1088,1097 ---- ResultRelInfo *resultRelInfo; /* per-subplan target relations */ List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */ EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */ ! bool firstCall; /* is this our first time through? */ ! ExprState *limitCount; /* LIMIT expression, if any */ ! int64_t maxProcessed; /* maximum number of rows we're allowed to process */ ! int64_t processed; /* number of rows we've processed so far */ } ModifyTableState; /* ---------------- *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1060,1065 **** typedef struct DeleteStmt --- 1060,1066 ---- Node *whereClause; /* qualifications */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ + Node *limitClause; /* LIMIT clause */ } DeleteStmt; /* ---------------------- *************** *** 1075,1080 **** typedef struct UpdateStmt --- 1076,1082 ---- List *fromClause; /* optional from clause for more tables */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ + Node *limitClause; /* LIMIT clause */ } UpdateStmt; /* ---------------------- *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** *** 177,182 **** typedef struct ModifyTable --- 177,183 ---- List *fdwPrivLists; /* per-target-table FDW private data lists */ List *rowMarks; /* PlanRowMarks (non-locking only) */ int epqParam; /* ID of Param for EvalPlanQual re-eval */ + Node *limitCount; /* maximum number of rows to process */ } ModifyTable; /* ---------------- *** a/src/include/optimizer/planmain.h --- b/src/include/optimizer/planmain.h *************** *** 84,90 **** extern ModifyTable *make_modifytable(PlannerInfo *root, CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *withCheckOptionLists, List *returningLists, ! List *rowMarks, int epqParam); extern bool is_projection_capable_plan(Plan *plan); /* --- 84,90 ---- CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *withCheckOptionLists, List *returningLists, ! List *rowMarks, int epqParam, Node *limitCount); extern bool is_projection_capable_plan(Plan *plan); /* *** a/src/test/regress/expected/delete.out --- b/src/test/regress/expected/delete.out *************** *** 31,33 **** SELECT id, a, char_length(b) FROM delete_test; --- 31,104 ---- (1 row) DROP TABLE delete_test; + -- LIMIT + CREATE TABLE delete_test AS + SELECT i*10 AS a FROM generate_series(1, 10) i; + DELETE FROM delete_test LIMIT -1; + ERROR: LIMIT must not be negative + DELETE FROM delete_test OFFSET 0; + ERROR: OFFSET is not supported with DELETE + LINE 1: DELETE FROM delete_test OFFSET 0; + ^ + DELETE FROM delete_test LIMIT 0; + SELECT count(*) FROM delete_test; + count + ------- + 10 + (1 row) + + DELETE FROM delete_test LIMIT 1; + SELECT count(*) FROM delete_test; + count + ------- + 9 + (1 row) + + DELETE FROM delete_test LIMIT (SELECT 1); + SELECT count(*) FROM delete_test; + count + ------- + 8 + (1 row) + + -- test against partitioned table + CREATE TABLE delete_test_child(a int) INHERITS (delete_test); + NOTICE: merging column "a" with inherited definition + INSERT INTO delete_test_child VALUES (5), (15), (25); + SELECT count(*) FROM delete_test; + count + ------- + 11 + (1 row) + + DELETE FROM delete_test LIMIT 5; + SELECT count(*) FROM delete_test; + count + ------- + 6 + (1 row) + + DELETE FROM delete_test LIMIT 5; + SELECT count(*) FROM delete_test; + count + ------- + 1 + (1 row) + + DELETE FROM delete_test LIMIT 5; + SELECT count(*) FROM delete_test; + count + ------- + 0 + (1 row) + + EXPLAIN (COSTS OFF) DELETE FROM delete_test LIMIT 1; + QUERY PLAN + ------------------------------------- + Delete on delete_test + -> Seq Scan on delete_test + -> Seq Scan on delete_test_child + (3 rows) + + DROP TABLE delete_test CASCADE; + NOTICE: drop cascades to table delete_test_child *** a/src/test/regress/expected/update.out --- b/src/test/regress/expected/update.out *************** *** 148,150 **** SELECT a, b, char_length(c) FROM update_test; --- 148,221 ---- (4 rows) DROP TABLE update_test; + -- LIMIT + CREATE TABLE update_test AS + SELECT i*10 AS a FROM generate_series(1, 10) i; + UPDATE update_test SET a = 0 LIMIT -1; + ERROR: LIMIT must not be negative + UPDATE update_test SET a = 0 OFFSET 0; + ERROR: OFFSET is not supported with UPDATE + LINE 1: UPDATE update_test SET a = 0 OFFSET 0; + ^ + UPDATE update_test SET a = -1 LIMIT 0; + SELECT a FROM update_test WHERE a = -1; + a + --- + (0 rows) + + UPDATE update_test SET a = -1 LIMIT 1; + SELECT a FROM update_test WHERE a = -1; + a + ---- + -1 + (1 row) + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT (SELECT 1); + SELECT a FROM update_test WHERE a = -1; + a + ---- + -1 + -1 + (2 rows) + + -- test against partitioned table + CREATE TABLE update_test_child(a int) INHERITS (update_test); + NOTICE: merging column "a" with inherited definition + INSERT INTO update_test_child VALUES (5), (15), (25); + SELECT count(*) FROM update_test WHERE a <> -1; + count + ------- + 11 + (1 row) + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT 5; + SELECT count(*) FROM update_test WHERE a <> -1; + count + ------- + 6 + (1 row) + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT 5; + SELECT count(*) FROM update_test WHERE a <> -1; + count + ------- + 1 + (1 row) + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT 5; + SELECT count(*) FROM update_test WHERE a <> -1; + count + ------- + 0 + (1 row) + + EXPLAIN (COSTS OFF) UPDATE update_test SET a = 10 LIMIT 1; + QUERY PLAN + ------------------------------------- + Update on update_test + -> Seq Scan on update_test + -> Seq Scan on update_test_child + (3 rows) + + DROP TABLE update_test CASCADE; + NOTICE: drop cascades to table update_test_child *** a/src/test/regress/sql/delete.sql --- b/src/test/regress/sql/delete.sql *************** *** 23,25 **** DELETE FROM delete_test WHERE a > 25; --- 23,61 ---- SELECT id, a, char_length(b) FROM delete_test; DROP TABLE delete_test; + + -- LIMIT + + CREATE TABLE delete_test AS + SELECT i*10 AS a FROM generate_series(1, 10) i; + + DELETE FROM delete_test LIMIT -1; + + DELETE FROM delete_test OFFSET 0; + + DELETE FROM delete_test LIMIT 0; + SELECT count(*) FROM delete_test; + + DELETE FROM delete_test LIMIT 1; + SELECT count(*) FROM delete_test; + + DELETE FROM delete_test LIMIT (SELECT 1); + SELECT count(*) FROM delete_test; + + -- test against partitioned table + CREATE TABLE delete_test_child(a int) INHERITS (delete_test); + INSERT INTO delete_test_child VALUES (5), (15), (25); + SELECT count(*) FROM delete_test; + + DELETE FROM delete_test LIMIT 5; + SELECT count(*) FROM delete_test; + + DELETE FROM delete_test LIMIT 5; + SELECT count(*) FROM delete_test; + + DELETE FROM delete_test LIMIT 5; + SELECT count(*) FROM delete_test; + + EXPLAIN (COSTS OFF) DELETE FROM delete_test LIMIT 1; + + DROP TABLE delete_test CASCADE; *** a/src/test/regress/sql/update.sql --- b/src/test/regress/sql/update.sql *************** *** 75,77 **** UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car'; --- 75,113 ---- SELECT a, b, char_length(c) FROM update_test; DROP TABLE update_test; + + -- LIMIT + + CREATE TABLE update_test AS + SELECT i*10 AS a FROM generate_series(1, 10) i; + + UPDATE update_test SET a = 0 LIMIT -1; + + UPDATE update_test SET a = 0 OFFSET 0; + + UPDATE update_test SET a = -1 LIMIT 0; + SELECT a FROM update_test WHERE a = -1; + + UPDATE update_test SET a = -1 LIMIT 1; + SELECT a FROM update_test WHERE a = -1; + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT (SELECT 1); + SELECT a FROM update_test WHERE a = -1; + + -- test against partitioned table + CREATE TABLE update_test_child(a int) INHERITS (update_test); + INSERT INTO update_test_child VALUES (5), (15), (25); + SELECT count(*) FROM update_test WHERE a <> -1; + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT 5; + SELECT count(*) FROM update_test WHERE a <> -1; + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT 5; + SELECT count(*) FROM update_test WHERE a <> -1; + + UPDATE update_test SET a = -1 WHERE a <> -1 LIMIT 5; + SELECT count(*) FROM update_test WHERE a <> -1; + + EXPLAIN (COSTS OFF) UPDATE update_test SET a = 10 LIMIT 1; + + DROP TABLE update_test CASCADE;