diff --git a/doc/src/sgml/ref/delete.sgml b/doc/src/sgml/ref/delete.sgml index 5d2ad3f..7acb862 100644 --- a/doc/src/sgml/ref/delete.sgml +++ b/doc/src/sgml/ref/delete.sgml @@ -25,6 +25,8 @@ PostgreSQL documentation DELETE FROM [ ONLY ] table_name [ * ] [ [ AS ] alias ] [ USING using_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] + [ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ] + [ LIMIT { count | ALL } ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] @@ -56,6 +58,24 @@ DELETE FROM [ ONLY ] table_name [ * + If the ORDER BY clause is present, the rows will be + processed in the specified order. In its absence, rows are + processed in whichever order the system finds fastest to + produce. + + + + If the LIMIT (or FETCH FIRST) clause + is present, processing will stop after the system has attempted + to delete the specified amount of rows. In particular, if a row + was concurrently changed not to match the given WHERE + clause, it will count towards the LIMIT despite it + not being actually deleted. 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 diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index 90b9208..ea75e55 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -27,6 +27,8 @@ UPDATE [ ONLY ] table_name [ * ] [ ( column_name [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...] [ FROM from_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] + [ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ] + [ LIMIT { count | ALL } ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] @@ -50,6 +52,24 @@ UPDATE [ ONLY ] table_name [ * ] [ + If the ORDER BY clause is present, the rows will be + processed in the specified order. In its absence, rows are + processed in whichever order the system finds fastest to + produce. + + + + If the LIMIT (or FETCH FIRST) clause + is present, processing will stop after the system has attempted + to update the specified amount of rows. In particular, if a row + was concurrently changed not to match the given WHERE + clause, it will count towards the LIMIT despite it + not being actually updated. 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. Any expression using the table's columns, and/or columns of other diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c89d808..cf9e66d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2516,6 +2516,8 @@ _copyDeleteStmt(const DeleteStmt *from) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(usingClause); COPY_NODE_FIELD(whereClause); + COPY_NODE_FIELD(sortClause); + COPY_NODE_FIELD(limitClause); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); @@ -2531,6 +2533,8 @@ _copyUpdateStmt(const UpdateStmt *from) COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(fromClause); + COPY_NODE_FIELD(sortClause); + COPY_NODE_FIELD(limitClause); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 9793cf5..aa7f203 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -894,6 +894,8 @@ _equalDeleteStmt(const DeleteStmt *a, const DeleteStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(usingClause); COMPARE_NODE_FIELD(whereClause); + COMPARE_NODE_FIELD(sortClause); + COMPARE_NODE_FIELD(limitClause); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); @@ -907,6 +909,8 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b) COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(fromClause); + COMPARE_NODE_FIELD(sortClause); + COMPARE_NODE_FIELD(limitClause); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 123f2a6..4f2a918 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2970,6 +2970,10 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->whereClause, context)) return true; + if (walker(stmt->sortClause, context)) + return true; + if (walker(stmt->limitClause, context)) + return true; if (walker(stmt->returningList, context)) return true; if (walker(stmt->withClause, context)) @@ -2988,6 +2992,10 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->fromClause, context)) return true; + if (walker(stmt->sortClause, context)) + return true; + if (walker(stmt->limitClause, context)) + return true; if (walker(stmt->returningList, context)) return true; if (walker(stmt->withClause, context)) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 7225bb6..b402892 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -386,6 +386,21 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); + /* + * Transform the sort clause. It might need to add items to our target + * list, but that's fine. + */ + qry->targetList = NIL; + qry->sortClause = transformSortClause(pstate, + stmt->sortClause, + &qry->targetList, + EXPR_KIND_ORDER_BY, + true, + false); + + qry->limitCount = transformLimitClause(pstate, stmt->limitClause, + EXPR_KIND_LIMIT, "LIMIT"); + qry->returningList = transformReturningList(pstate, stmt->returningList); /* done building the range table and jointree */ @@ -1941,8 +1956,27 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) nsitem->p_lateral_only = false; nsitem->p_lateral_ok = true; - qry->targetList = transformTargetList(pstate, stmt->targetList, - EXPR_KIND_UPDATE_SOURCE); + /* + * Transform the sort clause and the target list. As stmt->targetList + * contains the SET expressions and not actual columns of the underlying + * table, we have to transform the ORDER BY first, potentially adding + * resjunk columns to the target list. Afterwards the actual SET + * expressions from stmt->targetList are added to the end of the target + * list. + */ + qry->targetList = NIL; + qry->sortClause = transformSortClause(pstate, + stmt->sortClause, + &qry->targetList, + EXPR_KIND_ORDER_BY, + true, + false); + qry->targetList = list_concat(qry->targetList, + transformTargetList(pstate, + stmt->targetList, + EXPR_KIND_UPDATE_SOURCE)); + qry->limitCount = transformLimitClause(pstate, stmt->limitClause, + EXPR_KIND_LIMIT, "LIMIT"); qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 81169a4..7338d61 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -388,7 +388,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type opt_column event cursor_options opt_hold opt_set_data %type reindex_type drop_type comment_type security_label_type -%type fetch_args limit_clause select_limit_value +%type fetch_args limit_clause opt_limit_clause select_limit_value offset_clause select_offset_value select_offset_value2 opt_select_fetch_first_value %type row_or_rows first_or_next @@ -9121,13 +9121,15 @@ returning_clause: *****************************************************************************/ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias - using_clause where_or_current_clause returning_clause + using_clause where_or_current_clause opt_sort_clause opt_limit_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); n->relation = $4; n->usingClause = $5; n->whereClause = $6; - n->returningList = $7; + n->sortClause = $7; + n->limitClause = $8; + n->returningList = $9; n->withClause = $1; $$ = (Node *)n; } @@ -9187,6 +9189,8 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias SET set_clause_list from_clause where_or_current_clause + opt_sort_clause + opt_limit_clause returning_clause { UpdateStmt *n = makeNode(UpdateStmt); @@ -9194,7 +9198,9 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias n->targetList = $5; n->fromClause = $6; n->whereClause = $7; - n->returningList = $8; + n->sortClause = $8; + n->limitClause = $9; + n->returningList = $10; n->withClause = $1; $$ = (Node *)n; } @@ -9700,6 +9706,11 @@ limit_clause: { $$ = $3; } ; +opt_limit_clause: + limit_clause { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + offset_clause: OFFSET select_offset_value { $$ = $2; } diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index b5011af..ecf634d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1043,6 +1043,8 @@ typedef struct DeleteStmt RangeVar *relation; /* relation to delete from */ List *usingClause; /* optional using clause for more tables */ Node *whereClause; /* qualifications */ + List *sortClause; /* ORDER BY */ + Node *limitClause; /* LIMIT */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ } DeleteStmt; @@ -1058,6 +1060,8 @@ typedef struct UpdateStmt List *targetList; /* the target list (of ResTarget) */ Node *whereClause; /* qualifications */ List *fromClause; /* optional from clause for more tables */ + List *sortClause; /* ORDER BY */ + Node *limitClause; /* LIMIT */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ } UpdateStmt; diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index 71b856f..2f88190 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -40,6 +40,65 @@ SELECT * FROM update_test; 10 | 20 | (2 rows) +-- test ORDER BY +EXPLAIN (COSTS OFF) UPDATE update_test SET b = 10 ORDER BY b; + QUERY PLAN +------------------------------------- + Update on update_test + -> Sort + Sort Key: b + -> Seq Scan on update_test +(4 rows) + +EXPLAIN (COSTS OFF) UPDATE update_test t SET b = 10 ORDER BY t.b; + QUERY PLAN +--------------------------------------- + Update on update_test t + -> Sort + Sort Key: b + -> Seq Scan on update_test t +(4 rows) + +EXPLAIN (COSTS OFF) UPDATE update_test SET b = 10 ORDER BY columndoesnotexist; +ERROR: column "columndoesnotexist" does not exist +LINE 1: ...COSTS OFF) UPDATE update_test SET b = 10 ORDER BY columndoes... + ^ +EXPLAIN (COSTS OFF) UPDATE update_test t SET b = 10 ORDER BY t.b + 0; + QUERY PLAN +--------------------------------------- + Update on update_test t + -> Sort + Sort Key: ((b + 0)) + -> Seq Scan on update_test t +(4 rows) + +EXPLAIN (COSTS OFF) UPDATE update_test t SET b = 20 WHERE a = 10 ORDER BY t.b + 0; + QUERY PLAN +--------------------------------------- + Update on update_test t + -> Sort + Sort Key: ((b + 0)) + -> Seq Scan on update_test t + Filter: (a = 10) +(5 rows) + +EXPLAIN (COSTS OFF) UPDATE update_test t1 SET b = 20 FROM update_test t2 WHERE t1.a = t2.a ORDER BY t2.b; + QUERY PLAN +---------------------------------------------------- + Update on update_test t1 + -> Sort + Sort Key: t2.b + -> Merge Join + Merge Cond: (t1.a = t2.a) + -> Sort + Sort Key: t1.a + -> Seq Scan on update_test t1 + -> Sort + Sort Key: t2.a + -> Seq Scan on update_test t2 +(11 rows) + +UPDATE update_test t SET b = 20 WHERE a = 10 ORDER BY t.b + 0; -- -- Test VALUES in FROM -- diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index a8a028f..0c91f8f 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -27,6 +27,32 @@ UPDATE update_test t SET b = t.b + 10 WHERE t.a = 10; SELECT * FROM update_test; -- +-- test ORDER BY +-- +EXPLAIN (COSTS OFF) UPDATE update_test SET b = 10 ORDER BY b; + +EXPLAIN (COSTS OFF) UPDATE update_test t SET b = 10 ORDER BY t.b; + +EXPLAIN (COSTS OFF) UPDATE update_test SET b = 10 ORDER BY columndoesnotexist; + +EXPLAIN (COSTS OFF) UPDATE update_test t SET b = 10 ORDER BY t.b + 0; + +EXPLAIN (COSTS OFF) UPDATE update_test t SET b = 20 WHERE a = 10 ORDER BY t.b + 0; + +EXPLAIN (COSTS OFF) UPDATE update_test t1 SET b = 20 FROM update_test t2 WHERE t1.a = t2.a ORDER BY t2.b; + +UPDATE update_test t SET b = 20 WHERE a = 10 ORDER BY t.b + 0; + +-- +-- test LIMIT +-- +EXPLAIN (COSTS OFF) UPDATE update_test SET b = 10 LIMIT 1; + +EXPLAIN (COSTS OFF) UPDATE update_test SET b = 10 ORDER BY b LIMIT 1; + +UPDATE update_test SET b = 0 LIMIT 0 RETURNING *; + +-- -- Test VALUES in FROM --