From: | Bryn Llewellyn <bryn(at)yugabyte(dot)com> |
---|---|
To: | Adrian Klaver <adrian(dot)klaver(at)aklaver(dot)com> |
Cc: | pgsql-general list <pgsql-general(at)lists(dot)postgresql(dot)org> |
Subject: | Re: Is the PL/pgSQL refcursor useful in a modern three-tier app? |
Date: | 2023-03-20 20:46:58 |
Message-ID: | 8659357C-1DC6-4EB7-9451-3ECB04E6E67D@yugabyte.com |
Views: | Raw Message | Whole Thread | Download mbox | Resend email |
Thread: | |
Lists: | pgsql-general |
> adrian(dot)klaver(at)aklaver(dot)com wrote:
>
>> bryn(at)yugabyte(dot)com wrote:
>>
>>> adrian(dot)klaver(at)aklaver(dot)com wrote:
>>>
>>> I have a hard time fathoming why someone who writes documentation does not actually read documentation.
>>
>>
>> Ouch. In fact, I had read the whole of the "43.7. Cursors" section in the "PL/pgSQL" chapter. And the sections in the "SQL Commands" chapter for "declare", "fetch" and "close". But several of the key concepts didn't sink in and this prevented me not only from understanding what some of the examples showed but, worse, from being able to use the right vocabulary to express what confused me.
>
> Given this from your original question:
>
> « Anyway, without anything like Oracle PL/SQL's packages, you have no mechanism to hold the opened cursor variable between successive server calls. »
>
> What part of this [a particular code example] did not make sense in that context?
First off, despite the fact that I've clearly annoyed you (for which I apologize), I have found these exchanges very helpful. So thank you very much.
Your questions can be summarized as "Why couldn't you understand the doc? And why did you mix questions about use-cases with questions about the mechanics?" The answer has to do with psychology. I probably can't explain this convincingly. That's why it's taken me a long time to respond. I also had to do lots of testing before responding to make sure that the mental model that I've formed for myself is consistent with these.
You may not be interested in what follows. But, anyway, here goes.
— I happen to have many years of experience with Oracle Database and PL/SQL. The latter has analogous features to PL/pgSQL's "refcursor". But the differences between the notions in the two environments are enormous. My attempt to understand the latter was hindered by my understanding of the former. I accept that this is *my* problem and that I could never expect that the PG doc would cater for such a reader.
— Oracle Database doesn't expose scrollability for PL/SQL's equivalent of "refcursor". So I had never come across use cases where this was beneficial. I wanted, therefore, to hear about some. I thought that insights here would help me understand the mechanics. But I didn't get anything beyond "Scrollability is what it is. If you don't need it, don't use it."
Anyway, never mind all that now. Here's what I now (think that) I understand—with some comments on what made it hard for me to grasp.
— The key notion is what is referred to sometimes as "portal" and sometimes as "cursor". This is the thing that's global within, and private to, a session, that's uniquely identified by a bare name, that, and that's listed in "pg_cursors". I believe that in typical use, a cursor has only transaction duration. But (and only when you use the SQL API) you can create a cursor with (up to) session duration
— The doc pages for the "declare", "fetch", and "close" SQL statements don't mention "portal" and use only "cursor". They use the term to mean the underlying phenomenon and use wording like: "DECLARE allows a user to create cursors"; "You can see all available cursors by querying the pg_cursors system view"; "FETCH retrieves rows using a previously-created cursor"; "CLOSE frees the resources associated with an open cursor. After the cursor is closed, no subsequent operations are allowed on it. A cursor should be closed when it is no longer needed." However, these pages leave the term "open" undefined, though it's used. It seems that it has no meaning. Rather, a cursor with a particular name either exists or not. You create it with "declare" and drop it with "close". And that's it. If "open" means anything, it's just another word for "exists". (The fact that "pg_cursors" doesn't have a boolean column called "open" supports this understanding.) The sentence "After the cursor is closed, no subsequent operations are allowed on it." is equivalent to "After a table is dropped, no subsequent operations are allowed on it." But who would bother to say that? Notice that "pg_cursors" has a column called "creation_time" — and not "declaration time".
— On the other hand, the doc page "43.7. Cursors" uses "portal" a lot—and never says that it means exactly the same as "cursor" qua term of art (and not qua keyword). It does say "...a so-called portal containing the active query for the cursor." This suggests a subtle difference in meaning between "portal" and "cursor" and a notion of containment. I can't make any sense of that. It says things like "Before a cursor can be used to retrieve rows, it must be opened. (This is the equivalent action to the SQL command DECLARE CURSOR.)" This is the closest that it comes to saying that the SQL API and the PL/pgSQL API both manipulate the same thing—what you see in "pg_cursors". The sentence that I quoted is equivalent to saying "Before you can insert a row into a table, the table has to exist." In other words, an unhelpful tautology. I believe that the sense is this: « A variable with the data type "refcursor" holds a bare name (which is governed by the usual rules for a SQL name). The name might be found in "pg_cursors" or it might not be. When, and only when, it is found in "pg_cursors", the refcursor variable acts as a handle to the denoted cursor and supports operations upon it using various PL/pgSQL statements that use the identifier for the refcursor variable's name.
— I (but maybe only I) would have appreciated being able to read a single generic account that explained the underlying concepts. This would have prepared me for understanding the operations that the SQL and PL/pgSQL APIs expose—and especially that they are interoperable. So I'd've liked to see a note at the start of the four relevant sections ("43.7. Cursors" and the "declare", "fetch", and "close" SQL statements) that x-ref'd to the generic account and said "read this first".
Here's some more detail of how I'd state the mental model that I've deduced. Please tell me if you think that some, or all, of my account is wrong. (When I say "cursor", I always mean what's listed in "pg_cursors". And I'll never mention "portal" because the term seems to means exactly the same as "cursor".)
(1) A cursor must have a defining "select" statement. It also always has a pointer to the next-to-be-fetched row in the result set that the "select" defines. The rows are (implicitly) numbered from 1 through N where N is the number of rows that the cursor's "select" defines. However (as you can see from "fetch :x" in SQL, where :x is less than 1 or more than N) the pointer can point outside of the result set and not cause an error.
(2) A cursor defines a read-consistent snapshot, as of its "pg_cursors.creation_time". (From the "declare" doc, « In PostgreSQL, all cursors are insensitive. ») The complete set of rows that the "select" defines may not all be concurrently materialized in the cursor. This implies some kind of aging out and replacement implementation. The details aren't described because they have no semantic significance.
(3) In top-level SQL, you create a cursor with the "declare" statement. This lets you name it, specify its “select”, and specify a few other boolean attributes like "[ no ] scroll" and "{ with | without } hold".
(4) In PL/pgSQL, you create a cursor with "open". The operand is the identifier for the refcursor variable that holds the cursor's name. You can test your mental model by using the equivalent SQL statements with the "execute" PL/SQL statement.
(5) In top-level SQL, you drop a cursor with "close" where the operand is the identifier for the cursor's name. In PL/pgSQL, you drop a cursor with "close" where the operand is the identifier for the refcursor variable that holds the cursor's name.
(6) In PL/pgSQL, the value of a refcursor variable is an ordinary "text" value. It might be null. It you assign the name of a cursor that's listed in "pg_cursors" to a refcursor variable, then you can fetch from it or close it. And as long as the name isn't currently found in "pg_cursors", you can create a new row with that name with the "open" statement, specifying any "select" that you want.
(7) I found the terms "bound cursor" and "unbound cursor" (as in the section "43.7.2.3. Opening A Bound Cursor") initially very confusing because the wording connotes a property of a cursor—and "pg_cursors" has no column for such a notion. But I presently came to understand that this was a careless shorthand for "[un]bound cursor variable" — which phrases are also used on the same page.
(8) I found it initially hard to understand that the "bound" property of a refcursor variable is not part of its value. (And nor, for that matter, is the SQL statement that you specify with the "open" statement.) I reasoned, eventually, that the "bound" property must be an annotation of the variable in the AST for the block statement where the variable is declared. (Here, "declare" is used in the PL/pgSQL sense, and not the SQL sense, of the term). This explains why, when a function returns a refcursor value where the variable was declared as "bound", it can only be seen as "unbound" in a subprogram that has a refcursor formal argument. The same reasoning applies if you assign a bound refcursor variable to an unbound refcursor variable. (But I can't see that you'd have a reason to do that unless, like I was, you were testing your mental model.)
It's the fact that the value that a refcursor variable holds is nothing other than the text of a (potential) cursor's name (and that the SQL text and "bound" status are represented elsewhere) that lead me to write « without anything like Oracle PL/SQL's packages, you have no mechanism to hold the opened cursor variable between successive server calls ». When I wrote that, I thought, wrongly as I now see, that a refcursor variable held a composite, opaque value (or an opaque pointer to such) like it does in Oracle.
(9) The upshot of #8 is that the "FOR recordvar IN bound_cursorvar" construct can be used only in the block statement that declares the bound cursor variable. And this seems to defeat the point. You may just as well use an ordinary "for" loop that has the SQL statement right after the "in" keyword.
(10) I discovered that this construct:
for ... in select ... from pg_cursors order by name loop
...
end loop;
sees a cursor with an automatically generated name like "<unnamed portal N>" for the loop itself. I suppose that this makes good sense. But it does seem to undermine the value of declaring and using a bound cursor variable—esp as the nominal value of the "cursor" concept is the scrollability and the ability to fetch a smallish set of rows from anywhere in a huge result set.
From | Date | Subject | |
---|---|---|---|
Next Message | Adrian Klaver | 2023-03-20 22:38:54 | Re: Is the PL/pgSQL refcursor useful in a modern three-tier app? |
Previous Message | Ron Johnson | 2023-03-20 19:35:04 | Re: Schema/user/role |