From 5ddd771b0701f1f7b5e0976cf92283856dded1c3 Mon Sep 17 00:00:00 2001 From: Ryan Kuo Date: Wed, 7 May 2025 14:14:13 -0700 Subject: [PATCH 1/3] document CURSOR WITH HOLD --- .../v25.2/known-limitations/sql-cursors.md | 16 ----- src/current/v25.2/cursors.md | 67 +++++++++++++++++-- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/current/_includes/v25.2/known-limitations/sql-cursors.md b/src/current/_includes/v25.2/known-limitations/sql-cursors.md index 4c047aa9603..bceff96d5a6 100644 --- a/src/current/_includes/v25.2/known-limitations/sql-cursors.md +++ b/src/current/_includes/v25.2/known-limitations/sql-cursors.md @@ -3,22 +3,6 @@ CockroachDB implements SQL {% if page.name == "known-limitations.md" %} [cursor] - `DECLARE` only supports forward cursors. Reverse cursors created with `DECLARE SCROLL` are not supported. [#77102](https://github.com/cockroachdb/cockroach/issues/77102) - `FETCH` supports forward, relative, and absolute variants, but only for forward cursors. [#77102](https://github.com/cockroachdb/cockroach/issues/77102) - `BINARY CURSOR`, which returns data in the Postgres binary format, is not supported. [#77099](https://github.com/cockroachdb/cockroach/issues/77099) -- `WITH HOLD`, which allows keeping a cursor open for longer than a transaction by writing its results into a buffer, is accepted as valid syntax within a single transaction but is not supported. It acts as a no-op and does not actually perform the function of `WITH HOLD`, which is to make the cursor live outside its parent transaction. Instead, if you are using `WITH HOLD`, you will be forced to close that cursor within the transaction it was created in. [#77101](https://github.com/cockroachdb/cockroach/issues/77101) - - This syntax is accepted (but does not have any effect): - {% include_cached copy-clipboard.html %} - ~~~ sql - BEGIN; - DECLARE test_cur CURSOR WITH HOLD FOR SELECT * FROM foo ORDER BY bar; - CLOSE test_cur; - COMMIT; - ~~~ - - This syntax is not accepted, and will result in an error: - {% include_cached copy-clipboard.html %} - ~~~ sql - BEGIN; - DECLARE test_cur CURSOR WITH HOLD FOR SELECT * FROM foo ORDER BY bar; - COMMIT; -- This will fail with an error because CLOSE test_cur was not called inside the transaction. - ~~~ - Scrollable cursor (also known as reverse `FETCH`) is not supported. [#77102](https://github.com/cockroachdb/cockroach/issues/77102) - [`SELECT ... FOR UPDATE`]({% link {{ page.version.version }}/select-for-update.md %}) with a cursor is not supported. [#77103](https://github.com/cockroachdb/cockroach/issues/77103) - Respect for [`SAVEPOINT`s]({% link {{ page.version.version }}/savepoint.md %}) is not supported. Cursor definitions do not disappear properly if rolled back to a `SAVEPOINT` from before they were created. [#77104](https://github.com/cockroachdb/cockroach/issues/77104) diff --git a/src/current/v25.2/cursors.md b/src/current/v25.2/cursors.md index 09fac44776b..8f5bd1d3744 100644 --- a/src/current/v25.2/cursors.md +++ b/src/current/v25.2/cursors.md @@ -21,6 +21,7 @@ Cursors differ from [keyset pagination]({% link {{ page.version.version }}/pagin Cursors are declared and used with the following keywords: - [`DECLARE`]({% link {{ page.version.version }}/sql-grammar.md %}#declare_cursor_stmt) +- [`WITH HOLD`](#use-a-holdable-cursor) - [`FETCH`]({% link {{ page.version.version }}/sql-grammar.md %}#fetch_cursor_stmt) - [`CLOSE`]({% link {{ page.version.version }}/sql-grammar.md %}#close_cursor_stmt) @@ -75,6 +76,63 @@ CLOSE rides_cursor; COMMIT; ~~~ +### Use a holdable cursor + +By default, a cursor closes when the transaction ends. The `WITH HOLD` clause defines a *holdable cursor*, which stays open after a `COMMIT` by writing its results into a buffer. Use `WITH HOLD` to access data across multiple transactions without redefining the cursor. + +The following example uses a holdable cursor to return vehicles that are available for rides: + +Start a transaction and declare a cursor using `WITH HOLD` to keep it open after the `COMMIT`: + +{% include_cached copy-clipboard.html %} +~~~ sql +BEGIN; +DECLARE available_vehicles_cursor CURSOR WITH HOLD FOR + SELECT id, type, city, status FROM vehicles WHERE status = 'available'; +~~~ + +Fetch the first two rows from the cursor: + +{% include_cached copy-clipboard.html %} +~~~ sql +FETCH 2 FROM available_vehicles_cursor; +~~~ + +~~~ + id | type | city | status +---------------------------------------+---------+-----------+------------ + bbbbbbbb-bbbb-4800-8000-00000000000b | scooter | amsterdam | available + 22222222-2222-4200-8000-000000000002 | scooter | boston | available +~~~ + +Commit the transaction: + +{% include_cached copy-clipboard.html %} +~~~ sql +COMMIT; +~~~ + +Continue fetching rows from the cursor: + +{% include_cached copy-clipboard.html %} +~~~ sql +FETCH 2 FROM available_vehicles_cursor; +~~~ + +~~~ + id | type | city | status +---------------------------------------+------------+---------+------------ + 33333333-3333-4400-8000-000000000003 | bike | boston | available + 55555555-5555-4400-8000-000000000005 | skateboard | seattle | available +~~~ + +Close the cursor: + +{% include_cached copy-clipboard.html %} +~~~ sql +CLOSE available_vehicles_cursor; +~~~ + ### View all open cursors {% include_cached copy-clipboard.html %} @@ -83,10 +141,11 @@ SELECT * FROM pg_cursors; ~~~ ~~~ - name | statement | is_holdable | is_binary | is_scrollable | creation_time ----------------+---------------------+-------------+-----------+---------------+-------------------------------- - rides_cursor | SELECT * FROM rides | f | f | f | 2023-03-30 15:24:37.568054+00 -(1 row) + name | statement | is_holdable | is_binary | is_scrollable | creation_time +----------------------------+------------------------------------------------------------------------+-------------+-----------+---------------+-------------------------------- + rides_cursor | SELECT * FROM movr.rides | f | f | f | 2025-05-07 21:12:53.32978+00 + available_vehicles_cursor | SELECT id, type, city, status FROM vehicles WHERE status = 'available' | t | f | f | 2025-05-07 21:12:59.605647+00 +(2 rows) ~~~ ## Known limitations From 5b047e45be87a51bb5cee5e8a12a2dc488e371b2 Mon Sep 17 00:00:00 2001 From: Ryan Kuo Date: Thu, 8 May 2025 15:04:47 -0400 Subject: [PATCH 2/3] add implicit transaction example --- src/current/v25.2/cursors.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/current/v25.2/cursors.md b/src/current/v25.2/cursors.md index 8f5bd1d3744..4e7ad89f09c 100644 --- a/src/current/v25.2/cursors.md +++ b/src/current/v25.2/cursors.md @@ -78,15 +78,32 @@ COMMIT; ### Use a holdable cursor -By default, a cursor closes when the transaction ends. The `WITH HOLD` clause defines a *holdable cursor*, which stays open after a `COMMIT` by writing its results into a buffer. Use `WITH HOLD` to access data across multiple transactions without redefining the cursor. +By default, a cursor closes when the transaction ends. The `WITH HOLD` clause defines a *holdable cursor*, which stays open after a `COMMIT` by writing its results into a buffer. Use `WITH HOLD` to access data across multiple transactions without redefining the cursor. -The following example uses a holdable cursor to return vehicles that are available for rides: +{{site.data.alerts.callout_info}} +The `WITHOUT HOLD` clause specifies the default non-holdable cursor behavior. +{{site.data.alerts.end}} + +A holdable cursor can be opened in both explicit and implicit transactions. The following example uses a holdable cursor to return vehicles that are available for rides. -Start a transaction and declare a cursor using `WITH HOLD` to keep it open after the `COMMIT`: +
+ + +
+ +
+Start a transaction: {% include_cached copy-clipboard.html %} ~~~ sql BEGIN; +~~~ +
+ +Declare a cursor using `WITH HOLD` to keep it open after the `COMMIT`: + +{% include_cached copy-clipboard.html %} +~~~ sql DECLARE available_vehicles_cursor CURSOR WITH HOLD FOR SELECT id, type, city, status FROM vehicles WHERE status = 'available'; ~~~ @@ -105,12 +122,14 @@ FETCH 2 FROM available_vehicles_cursor; 22222222-2222-4200-8000-000000000002 | scooter | boston | available ~~~ +
Commit the transaction: {% include_cached copy-clipboard.html %} ~~~ sql COMMIT; ~~~ +
Continue fetching rows from the cursor: From 943ddb457e7b7357cb166ba9dac788bfd592cd2a Mon Sep 17 00:00:00 2001 From: Ryan Kuo Date: Fri, 9 May 2025 12:36:06 -0400 Subject: [PATCH 3/3] address reviewer feedback --- src/current/v25.2/cursors.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/current/v25.2/cursors.md b/src/current/v25.2/cursors.md index 4e7ad89f09c..a32b246dff8 100644 --- a/src/current/v25.2/cursors.md +++ b/src/current/v25.2/cursors.md @@ -78,13 +78,20 @@ COMMIT; ### Use a holdable cursor -By default, a cursor closes when the transaction ends. The `WITH HOLD` clause defines a *holdable cursor*, which stays open after a `COMMIT` by writing its results into a buffer. Use `WITH HOLD` to access data across multiple transactions without redefining the cursor. +By default, a cursor closes when the transaction ends. The `WITH HOLD` clause defines a *holdable cursor*, which behaves as follows: + +- A holdable cursor writes its results into a buffer, and stays open after a transaction commits. +- If a transaction aborts, all cursors opened within that transaction are also rolled back. However, holdable cursors from previously committed transactions remain open. +- A holdable cursor can be opened in both explicit and implicit transactions. +- If a holdable cursor results in an error as it is being persisted, it will cause the current transaction (implicit or explicit) to be rolled back. + +Use `WITH HOLD` to access data across multiple transactions without redefining the cursor. {{site.data.alerts.callout_info}} The `WITHOUT HOLD` clause specifies the default non-holdable cursor behavior. {{site.data.alerts.end}} -A holdable cursor can be opened in both explicit and implicit transactions. The following example uses a holdable cursor to return vehicles that are available for rides. +The following example uses a holdable cursor to return vehicles that are available for rides.
@@ -100,7 +107,7 @@ BEGIN; ~~~ -Declare a cursor using `WITH HOLD` to keep it open after the `COMMIT`: +Declare a cursor using `WITH HOLD` to keep it open after the transaction commits: {% include_cached copy-clipboard.html %} ~~~ sql