diff --git a/doc/src/sgml/ref/delete.sgml b/doc/src/sgml/ref/delete.sgml index 20417a1..1b4195c 100644 --- a/doc/src/sgml/ref/delete.sgml +++ b/doc/src/sgml/ref/delete.sgml @@ -25,6 +25,9 @@ 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 } ] + [ OFFSET start [ ROW | ROWS ] ] ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] @@ -143,6 +146,76 @@ DELETE FROM [ ONLY ] table_name [ * + + If the ORDER BY clause is specified, the + deleted rows are sorted in the specified order. + + + + Optionally one can add the key word ASC (ascending) or + DESC (descending) after any expression in the + ORDER BY clause. If not specified, ASC is + assumed by default. Alternatively, a specific ordering operator + name can be specified in the USING clause. + An ordering operator must be a less-than or greater-than + member of some B-tree operator family. + ASC is usually equivalent to USING < and + DESC is usually equivalent to USING >. + (But the creator of a user-defined data type can define exactly what the + default sort ordering is, and it might correspond to operators with other + names.) + + + + If NULLS LAST is specified, null values sort after all + non-null values; if NULLS FIRST is specified, null values + sort before all non-null values. If neither is specified, the default + behavior is NULLS LAST when ASC is specified + or implied, and NULLS FIRST when DESC is specified + (thus, the default is to act as though nulls are larger than non-nulls). + When USING is specified, the default nulls ordering depends + on whether the operator is a less-than or greater-than operator. + + + + Note that ordering options apply only to the expression they follow; + for example ORDER BY x, y DESC does not mean + the same thing as ORDER BY x DESC, y DESC. + + + + Character-string data is sorted according to the collation that applies + to the column being sorted. That can be overridden at need by including + a COLLATE clause in the + expression, for example + ORDER BY mycolumn COLLATE "en_US". + For more information see and + . + + + + count + start + + + count specifies the + maximum number of rows to delete, while start specifies the number of rows + to skip before starting to delete rows. When both are specified, + start rows are skipped + before starting to count the count rows to be deleted. + + + + If the count expression + evaluates to NULL, it is treated as LIMIT ALL, i.e., no + limit. If start evaluates + to NULL, it is treated the same as OFFSET 0. + + + + cursor_name diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index 8a1619f..8d6f8d7 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -29,6 +29,9 @@ UPDATE [ ONLY ] table_name [ * ] [ } [, ...] [ FROM from_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] + [ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] + [ LIMIT { count | ALL } ] + [ OFFSET start [ ROW | ROWS ] ] ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] @@ -190,6 +193,76 @@ UPDATE [ ONLY ] table_name [ * ] [ + + If the ORDER BY clause is specified, the + updated rows are sorted in the specified order. + + + + Optionally one can add the key word ASC (ascending) or + DESC (descending) after any expression in the + ORDER BY clause. If not specified, ASC is + assumed by default. Alternatively, a specific ordering operator + name can be specified in the USING clause. + An ordering operator must be a less-than or greater-than + member of some B-tree operator family. + ASC is usually equivalent to USING < and + DESC is usually equivalent to USING >. + (But the creator of a user-defined data type can define exactly what the + default sort ordering is, and it might correspond to operators with other + names.) + + + + If NULLS LAST is specified, null values sort after all + non-null values; if NULLS FIRST is specified, null values + sort before all non-null values. If neither is specified, the default + behavior is NULLS LAST when ASC is specified + or implied, and NULLS FIRST when DESC is specified + (thus, the default is to act as though nulls are larger than non-nulls). + When USING is specified, the default nulls ordering depends + on whether the operator is a less-than or greater-than operator. + + + + Note that ordering options apply only to the expression they follow; + for example ORDER BY x, y DESC does not mean + the same thing as ORDER BY x DESC, y DESC. + + + + Character-string data is sorted according to the collation that applies + to the column being sorted. That can be overridden at need by including + a COLLATE clause in the + expression, for example + ORDER BY mycolumn COLLATE "en_US". + For more information see and + . + + + + count + start + + + count specifies the + maximum number of rows to update, while start specifies the number of rows + to skip before starting to update rows. When both are specified, + start rows are skipped + before starting to count the count rows to be updated. + + + + If the count expression + evaluates to NULL, it is treated as LIMIT ALL, i.e., no + limit. If start evaluates + to NULL, it is treated the same as OFFSET 0. + + + + cursor_name diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 00a0fed..1a8dfc6 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2996,6 +2996,9 @@ _copyDeleteStmt(const DeleteStmt *from) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(usingClause); COPY_NODE_FIELD(whereClause); + COPY_NODE_FIELD(sortClause); + COPY_NODE_FIELD(limitOffset); + COPY_NODE_FIELD(limitCount); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); @@ -3011,6 +3014,9 @@ _copyUpdateStmt(const UpdateStmt *from) COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(fromClause); + COPY_NODE_FIELD(sortClause); + COPY_NODE_FIELD(limitOffset); + COPY_NODE_FIELD(limitCount); COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(withClause); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 46573ae..2136be3 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1023,6 +1023,9 @@ _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(limitOffset); + COMPARE_NODE_FIELD(limitCount); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); @@ -1036,6 +1039,9 @@ _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(limitOffset); + COMPARE_NODE_FIELD(limitCount); COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(withClause); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 3e8189c..3c73295 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3403,6 +3403,12 @@ 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->limitOffset, context)) + return true; + if (walker(stmt->limitCount, context)) + return true; if (walker(stmt->returningList, context)) return true; if (walker(stmt->withClause, context)) @@ -3421,6 +3427,12 @@ 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->limitOffset, context)) + return true; + if (walker(stmt->limitCount, 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 567dd54..fca98b3 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -432,6 +432,19 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); + qry->sortClause = transformSortClause(pstate, + stmt->sortClause, + &qry->targetList, + EXPR_KIND_ORDER_BY, + false /* allow SQL92 rules */ ); + + /* transform LIMIT */ + qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, + EXPR_KIND_OFFSET, "OFFSET"); + qry->limitCount = transformLimitClause(pstate, stmt->limitCount, + EXPR_KIND_LIMIT, "LIMIT"); + + qry->returningList = transformReturningList(pstate, stmt->returningList); /* done building the range table and jointree */ @@ -2246,6 +2259,17 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) */ qry->targetList = transformUpdateTargetList(pstate, stmt->targetList); + qry->sortClause = transformSortClause(pstate, + stmt->sortClause, + &qry->targetList, + EXPR_KIND_ORDER_BY, + false /* allow SQL92 rules */ ); + /* transform LIMIT */ + qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, + EXPR_KIND_OFFSET, "OFFSET"); + qry->limitCount = transformLimitClause(pstate, stmt->limitCount, + EXPR_KIND_LIMIT, "LIMIT"); + qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 89d2836..de446ff 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -396,7 +396,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); relation_expr_list dostmt_opt_list transform_element_list transform_type_list TriggerTransitions TriggerReferencing - publication_name_list + publication_name_list opt_addtional_criteria %type group_by_list %type group_by_item empty_grouping_set rollup_clause cube_clause @@ -10632,13 +10632,16 @@ 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_addtional_criteria returning_clause { DeleteStmt *n = makeNode(DeleteStmt); n->relation = $4; n->usingClause = $5; n->whereClause = $6; - n->returningList = $7; + n->limitCount = list_nth($7, 1); + n->limitOffset = list_nth($7, 2); + n->sortClause =list_nth($7, 0); + n->returningList = $8; n->withClause = $1; $$ = (Node *)n; } @@ -10704,6 +10707,7 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias SET set_clause_list from_clause where_or_current_clause + opt_addtional_criteria returning_clause { UpdateStmt *n = makeNode(UpdateStmt); @@ -10711,7 +10715,10 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias n->targetList = $5; n->fromClause = $6; n->whereClause = $7; - n->returningList = $8; + n->limitCount = list_nth($8, 1); + n->limitOffset = list_nth($8, 2); + n->sortClause =list_nth($8, 0); + n->returningList = $9; n->withClause = $1; $$ = (Node *)n; } @@ -11253,6 +11260,13 @@ select_offset_value: a_expr { $$ = $1; } ; +opt_addtional_criteria: + sort_clause limit_clause offset_clause { $$ = list_make3($1, $2, $3); } + | sort_clause offset_clause { $$ = list_make3($1, NULL, $2); } + | sort_clause limit_clause { $$ = list_make3($1, $2, NULL); } + | /*EMPTY*/ { $$ = list_make3(NULL, NULL, NULL); } + ; + /* * Allowing full expressions without parentheses causes various parsing * problems with the trailing ROW/ROWS key words. SQL only calls for diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 9f57388..c327303 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1427,6 +1427,9 @@ typedef struct DeleteStmt RangeVar *relation; /* relation to delete from */ List *usingClause; /* optional using clause for more tables */ Node *whereClause; /* qualifications */ + List *sortClause; /* sort clause (a list of SortBy's) */ + Node *limitOffset; /* # of result tuples to skip */ + Node *limitCount; /* # of result tuples to delete */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ } DeleteStmt; @@ -1442,6 +1445,9 @@ typedef struct UpdateStmt List *targetList; /* the target list (of ResTarget) */ Node *whereClause; /* qualifications */ List *fromClause; /* optional from clause for more tables */ + List *sortClause; /* sort clause (a list of SortBy's) */ + Node *limitOffset; /* # of result tuples to skip */ + Node *limitCount; /* # of result tuples to update */ List *returningList; /* list of expressions to return */ WithClause *withClause; /* WITH clause */ } UpdateStmt; diff --git a/src/test/regress/expected/delete.out b/src/test/regress/expected/delete.out index e7eb328..ccb1c0b 100644 --- a/src/test/regress/expected/delete.out +++ b/src/test/regress/expected/delete.out @@ -30,4 +30,25 @@ SELECT id, a, char_length(b) FROM delete_test; 1 | 10 | (1 row) +-- +-- Test ORDER BY with limit clause options +-- +INSERT INTO delete_test (a, b) VALUES (1,'x'),(1,'xx'),(1,'xxx'),(1,'xxxx'); +DELETE FROM delete_test WHERE a=1 ORDER BY id LIMIT 2 OFFSET 1; +SELECT * FROM delete_test; + id | a | b +----+----+------ + 1 | 10 | + 4 | 1 | x + 7 | 1 | xxxx +(3 rows) + +DELETE FROM delete_test WHERE a=1 ORDER BY id LIMIT 1; +SELECT * FROM delete_test; + id | a | b +----+----+------ + 1 | 10 | + 7 | 1 | xxxx +(2 rows) + DROP TABLE delete_test; diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out index 9366f04..36888e4 100644 --- a/src/test/regress/expected/update.out +++ b/src/test/regress/expected/update.out @@ -196,6 +196,31 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a) 1 | Foo, Correlated, Excluded (1 row) +-- +-- Test ORDER BY with limit clause options +-- +delete from update_test; +INSERT INTO update_test (b, c) VALUES (1,'x'),(2,'xx'),(3,'xxx'),(4,'xxxx'); +UPDATE update_test SET c ='xxxxx' WHERE a = 10 ORDER BY b LIMIT 2 OFFSET 1; +SELECT * FROM update_test; + a | b | c +----+---+------- + 10 | 1 | x + 10 | 4 | xxxx + 10 | 2 | xxxxx + 10 | 3 | xxxxx +(4 rows) + +UPDATE update_test SET c ='xxxxx' WHERE a = 10 ORDER BY b LIMIT 1; +SELECT * FROM update_test; + a | b | c +----+---+------- + 10 | 4 | xxxx + 10 | 2 | xxxxx + 10 | 3 | xxxxx + 10 | 1 | xxxxx +(4 rows) + DROP TABLE update_test; DROP TABLE upsert_test; -- update to a partition should check partition bound constraint for the new tuple diff --git a/src/test/regress/sql/delete.sql b/src/test/regress/sql/delete.sql index d8cb99e..bf19390 100644 --- a/src/test/regress/sql/delete.sql +++ b/src/test/regress/sql/delete.sql @@ -22,4 +22,17 @@ DELETE FROM delete_test WHERE a > 25; SELECT id, a, char_length(b) FROM delete_test; +-- +-- Test ORDER BY with limit clause options +-- +INSERT INTO delete_test (a, b) VALUES (1,'x'),(1,'xx'),(1,'xxx'),(1,'xxxx'); + +DELETE FROM delete_test WHERE a=1 ORDER BY id LIMIT 2 OFFSET 1; + +SELECT * FROM delete_test; + +DELETE FROM delete_test WHERE a=1 ORDER BY id LIMIT 1; + +SELECT * FROM delete_test; + DROP TABLE delete_test; diff --git a/src/test/regress/sql/update.sql b/src/test/regress/sql/update.sql index 6637119..7f780d1 100644 --- a/src/test/regress/sql/update.sql +++ b/src/test/regress/sql/update.sql @@ -104,6 +104,21 @@ INSERT INTO upsert_test VALUES (1, 'Bat') ON CONFLICT(a) DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a) RETURNING *; +-- +-- Test ORDER BY with limit clause options +-- +delete from update_test; + +INSERT INTO update_test (b, c) VALUES (1,'x'),(2,'xx'),(3,'xxx'),(4,'xxxx'); + +UPDATE update_test SET c ='xxxxx' WHERE a = 10 ORDER BY b LIMIT 2 OFFSET 1; + +SELECT * FROM update_test; + +UPDATE update_test SET c ='xxxxx' WHERE a = 10 ORDER BY b LIMIT 1; + +SELECT * FROM update_test; + DROP TABLE update_test; DROP TABLE upsert_test;