*** 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;