Thread: MOVE strangeness

MOVE strangeness

From
"Jeroen T. Vermeulen"
Date:
Here's something that's been bothering me for a while...  Perhaps this
is correct behaviour, but I can't quite see how.  This does not happen
if I replace the FETCHes by MOVEs.

Here's the reference case:

jtv=> begin;
BEGIN
jtv=> declare c cursor for select * from events;
DECLARE CURSOR
jtv=> fetch 3 in c;year |      event       
------+------------------2010 | A Space Oddyssey2010 | Oddyssey Two2038 | time_t overflow
(3 rows)

jtv=> move -3 in c;
MOVE 2
jtv=> fetch 3 in c;year |      event       
------+------------------2010 | A Space Oddyssey2010 | Oddyssey Two2038 | time_t overflow
(3 rows)

jtv=> end;
COMMIT


Okay, since that "move -3" claims to have moved only 2 rows backwards, I
tried the same but moving backwards by only 2 rows.  This gives me the
same response for the MOVE, but my cursor evidently doesn't end up in
the same place:


jtv=> begin;
BEGIN
jtv=> declare c cursor for select * from events;
DECLARE CURSOR
jtv=> fetch 3 in c;year |      event       
------+------------------2010 | A Space Oddyssey2010 | Oddyssey Two2038 | time_t overflow
(3 rows)

jtv=> move -2 in c;
MOVE 2
jtv=> fetch 3 in c;year |      event      
------+-----------------2010 | Oddyssey Two2038 | time_t overflow1971 | jtv
(3 rows)

jtv=> end;
COMMIT


This makes it a bit hard for me to figure out just how far I moved my
cursor backwards!  Moving by BACKWARD ALL will give me the same result
as moving by -3.

Is this behaviour intentional?  If not, can it be fixed?


Jeroen




Re: MOVE strangeness

From
Tom Lane
Date:
"Jeroen T. Vermeulen" <jtv@xs4all.nl> writes:
> Here's something that's been bothering me for a while...  Perhaps this
> is correct behaviour, but I can't quite see how.

It looks fine to me, given the underlying model of how a cursor works,
which probably isn't really written down anywhere :-(.  Briefly:

1. A cursor can be positioned before the first row, after the last row,
or on any individual row of its SELECT result.  The initial state is
before the first row.

2. "FETCH 1" advances the cursor one row (if possible), and if it's not
now after the last row, returns the row the cursor is now on.  "FETCH n"
repeats this operation n times.

3. "FETCH BACKWARD 1" (or FETCH -1) moves the cursor back one row (if
possible), and if it's not now before the first row, returns the row the
cursor is now on.  "FETCH -n" repeats this operation n times.

4. The result count is the number of rows successfully returned (or for
MOVE, the number that would have been returned by the equivalent FETCH).


> This makes it a bit hard for me to figure out just how far I moved my
> cursor backwards!  Moving by BACKWARD ALL will give me the same result
> as moving by -3.

If the return count is not the same as abs(n), then you ran off the end
of the result, and are now positioned before the start or after the end
depending on the requested direction.  If the return count is the same
as abs(n), then you are positioned on a real row.  This doesn't seem any
more confusing to me than any other convention that might have been
picked.

> This does not happen
> if I replace the FETCHes by MOVEs.

I'm a little confused by that remark; it seems to me that FETCH and MOVE
have identical behaviors so far as repositioning the cursor is concerned.
(Internally, MOVE *is* a FETCH, it just suppresses output of the rows.)
Can you give an example where you get different behavior?
        regards, tom lane


Re: MOVE strangeness

From
Bruce Momjian
Date:
Tom Lane wrote:
> > This does not happen
> > if I replace the FETCHes by MOVEs.
> 
> I'm a little confused by that remark; it seems to me that FETCH and MOVE
> have identical behaviors so far as repositioning the cursor is concerned.
> (Internally, MOVE *is* a FETCH, it just suppresses output of the rows.)
> Can you give an example where you get different behavior?

I think I see what Jeroen is saying.  In this example he posted:jtv=> begin;BEGINjtv=> declare c cursor for select *
fromevents;DECLARE CURSORjtv=> fetch 3 in c; year |      event       ------+------------------ 2010 | A Space Oddyssey
2010| Oddyssey Two 2038 | time_t overflow(3 rows)jtv=> move -3 in c;MOVE 2
 

Why does the MOVE -3 return 2?  If he has fetched 3, he is at the end of
the third row.  If he does MOVE -3, hasn't he moved backward three rows
to the start of the first row?

--  Bruce Momjian                        |  http://candle.pha.pa.us pgman@candle.pha.pa.us               |  (610)
359-1001+  If your life is a hard drive,     |  13 Roberts Road +  Christ can be your backup.        |  Newtown Square,
Pennsylvania19073
 


Re: MOVE strangeness

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Why does the MOVE -3 return 2?

Because he's successfully backed up over 2 real rows.  Had he done FETCH
-3 in the same situation, he'd have gotten back 2 rows; there is no
third row it could have returned, so it's hard to argue that the count
should be anything but 2.  (If you think it should be 3, what if I say
MOVE -10000?  Should I get back a count of 10000?)

> If he has fetched 3, he is at the end of
> the third row.  If he does MOVE -3, hasn't he moved backward three rows
> to the start of the first row?

There is no "end of a row" as distinct from "start of a row".  You can
be on a row, or before the first row, or after the last row.  There is
no other state besides that.
        regards, tom lane


Re: MOVE strangeness

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Why does the MOVE -3 return 2?
> 
> Because he's successfully backed up over 2 real rows.  Had he done FETCH
> -3 in the same situation, he'd have gotten back 2 rows; there is no
> third row it could have returned, so it's hard to argue that the count
> should be anything but 2.  (If you think it should be 3, what if I say
> MOVE -10000?  Should I get back a count of 10000?)

Sorry, I am not understanding.  If he does:jtv=> begin;BEGINjtv=> declare c cursor for select * from events;DECLARE
CURSORjtv=>fetch 3 in c; year |      event       ------+------------------ 2010 | A Space Oddyssey 2010 | Oddyssey Two
2038| time_t overflow(3 rows)
 

here, isn't he sitting at the start of the fourth row, no?  When he does
MOVE -3, doesn't he move from the start of the 4th row to the start of
the 1st row?jtv=> move -3 in c;MOVE 2

> > If he has fetched 3, he is at the end of
> > the third row.  If he does MOVE -3, hasn't he moved backward three rows
> > to the start of the first row?
> 
> There is no "end of a row" as distinct from "start of a row".  You can
> be on a row, or before the first row, or after the last row.  There is
> no other state besides that.

So, you are saying if he does a FETCH 3, he is still sitting on the 3rd
row, rather than at the gap between the 3rd and 4th rows?  If so, the
behavior does make sense.

--  Bruce Momjian                        |  http://candle.pha.pa.us pgman@candle.pha.pa.us               |  (610)
359-1001+  If your life is a hard drive,     |  13 Roberts Road +  Christ can be your backup.        |  Newtown Square,
Pennsylvania19073
 


Re: MOVE strangeness

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Sorry, I am not understanding.  If he does:
> ...
> here, isn't he sitting at the start of the fourth row, no?

No.  He is sitting *on* the third row.  If he now does FETCH 1, he will
advance to and return the fourth row; on the other hand, if he does
FETCH -1, he will back up to and return the second row.

The cursor must be considered to be positioned on its current row, not
between rows, or the SQL-defined operations UPDATE WHERE CURRENT OF and
DELETE WHERE CURRENT OF don't make any sense.  (We don't support those
yet, but we should someday.)

BTW, looking at Date and the SQL spec, I now realize that the recently
made change to convert FETCH 0 into a no-op is wrong; per spec, FETCH
RELATIVE 0 means "re-fetch the current row, if any".  By analogy, MOVE 0
should probably return "MOVE 1" if you are on a real row, "MOVE 0" if
you are not, corresponding to the number of rows you'd have gotten from
FETCH 0.  Ugly, but ...
        regards, tom lane


Re: MOVE strangeness

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Sorry, I am not understanding.  If he does:
> > ...
> > here, isn't he sitting at the start of the fourth row, no?
> 
> No.  He is sitting *on* the third row.  If he now does FETCH 1, he will
> advance to and return the fourth row; on the other hand, if he does
> FETCH -1, he will back up to and return the second row.

OK, and it makes sense FETCH -1 will move back a row rather than
re-reading the row.

> The cursor must be considered to be positioned on its current row, not
> between rows, or the SQL-defined operations UPDATE WHERE CURRENT OF and
> DELETE WHERE CURRENT OF don't make any sense.  (We don't support those
> yet, but we should someday.)

Yes, that's where the positioning makes sense.

> BTW, looking at Date and the SQL spec, I now realize that the recently
> made change to convert FETCH 0 into a no-op is wrong; per spec, FETCH
> RELATIVE 0 means "re-fetch the current row, if any".  By analogy, MOVE 0
> should probably return "MOVE 1" if you are on a real row, "MOVE 0" if
> you are not, corresponding to the number of rows you'd have gotten from
> FETCH 0.  Ugly, but ...

OK, I will fix those.  I am working on it now.  I think I am going to
have to break the internal representation that a zero fetch means fetch
all.  Right now, we use INT_MAX for fetch all in PerformPortalFetch.

--  Bruce Momjian                        |  http://candle.pha.pa.us pgman@candle.pha.pa.us               |  (610)
359-1001+  If your life is a hard drive,     |  13 Roberts Road +  Christ can be your backup.        |  Newtown Square,
Pennsylvania19073
 


Re: MOVE strangeness

From
"Jeroen T. Vermeulen"
Date:
On Thu, Dec 26, 2002 at 02:14:40PM -0500, Tom Lane wrote:                       
> Bruce Momjian <pgman@candle.pha.pa.us> writes:                                
> > Sorry, I am not understanding.  If he does:                                 
> > ...                                                                         
> > here, isn't he sitting at the start of the fourth row, no?                  
>                                                                               
> No.  He is sitting *on* the third row.  If he now does FETCH 1, he will       
> advance to and return the fourth row; on the other hand, if he does           
> FETCH -1, he will back up to and return the second row.
                                      
 
So we're talking about pre-increment and pre-decrement, and a cursor            
starting at position 0 in a 1-based array (or -1 in a 0-based one)?
                                                                                                                     
 
> BTW, looking at Date and the SQL spec, I now realize that the recently        
> made change to convert FETCH 0 into a no-op is wrong; per spec, FETCH         
> RELATIVE 0 means "re-fetch the current row, if any".  By analogy, MOVE 0      
> should probably return "MOVE 1" if you are on a real row, "MOVE 0" if         
> you are not, corresponding to the number of rows you'd have gotten from       
> FETCH 0.  Ugly, but ...
                                      
 
Okay, given that, is there really any reason why MOVE should return the         
number of rows that would have been fetched?  Why not report the number         
of rows moved?  Having two different MOVE commands from the same starting       
positions yield indistinguishable results yet bring you to different            
states is very, very awkward and possibly quite useless.  Better yet,           
why should FETCH report the number of rows fetched instead of the               
number of rows moved when you're going to extract the former from the           
PQresult's PQntuples() anyway?  The current status string is completely         
redundant in that case.
                                      
 
If the status string for MOVE is both inconclusive (as my case shows)           
and inconsistent (the MOVE 0 case) now, but it's linked to the status           
string for FETCH which is redundant, it seems logical to give them              
new, consistent semantics that are useful and intuitive.  Just report           
the number of rows _moved_ and we can all go back to lead rich,                 
meaningful lives.  I for one will also sleep better knowing that the            
number of rows reported is based on the same counting system as the             
number of rows requested in the SQL command that the status string is           
supposed to echo.  If FETCH 0 expects to fetch 1 row, why not have a            
result set of 1 row and a result string that echoes the command?
                                      
 
Which begs the question: is there anything special about the one-before-        
first row or can a cursor be moved to any arbitrary point outside its           
actual set of rows?  Oh, what odious web we weave / when first we               
practice to iteratewithoutthebenefitsofzero-basedarithmeticandhalf-             
openintervals.
                                                                                                                     
 
Jeroen



Re: MOVE strangeness

From
Tom Lane
Date:
"Jeroen T. Vermeulen" <jtv@xs4all.nl> writes:
> Okay, given that, is there really any reason why MOVE should return the
> number of rows that would have been fetched?  Why not report the number
> of rows moved?  Having two different MOVE commands from the same starting
> positions yield indistinguishable results yet bring you to different
> states is very, very awkward and possibly quite useless.  Better yet,
> why should FETCH report the number of rows fetched instead of the
> number of rows moved when you're going to extract the former from the
> PQresult's PQntuples() anyway?

The main reason why neither of these are likely to change is that it
will break existing, working applications if we change it.  "Why not"
is not an argument that will win out against that point ... especially
not when it's debatable whether the proposed change is actually an
improvement.
        regards, tom lane


Re: MOVE strangeness

From
Kevin Brown
Date:
Tom Lane wrote:
> The cursor must be considered to be positioned on its current row, not
> between rows, or the SQL-defined operations UPDATE WHERE CURRENT OF and
> DELETE WHERE CURRENT OF don't make any sense.  (We don't support those
> yet, but we should someday.)

Okay.  But then doesn't it make sense for FETCH to fetch the contents
of the row (and subsequent requested rows) that the cursor is
currently on *then* move, and not the other way around?  Were that the
model, then the only situation in which the cursor would not be on a
row is if it's past the last one (or if it pointed to an empty set).
And the initial declaration of the cursor would thus normally place
the cursor on the first row fetched, just as you'd expect.

"Fetch 3" would fetch whatever row the cursor is currently on and the
following 2 rows, then move the cursor down 3 rows (so that it's on
the row that follows the last row fetched).

Additionally, with that model, UPDATE WHERE CURRENT OF would work
exactly as you'd expect in all situations: it would update the row the
cursor is on or, if the cursor is beyond the last row, would fail.
MOVE would also work exactly as you'd expect: MOVE -3 would move the
cursor back 3 positions, or to the beginning of the set, whichever
comes first, and would report how many positions it was able to move.


My read of the spec indicates that cursors don't behave the way I
describe above.  If I'm right, then does anyone here know why the spec
calls for something different?


-- 
Kevin Brown                          kevin@sysexperts.com


Re: MOVE strangeness

From
Tom Lane
Date:
Kevin Brown <kevin@sysexperts.com> writes:
> Tom Lane wrote:
>> The cursor must be considered to be positioned on its current row, not
>> between rows, or the SQL-defined operations UPDATE WHERE CURRENT OF and
>> DELETE WHERE CURRENT OF don't make any sense.  (We don't support those
>> yet, but we should someday.)

> Okay.  But then doesn't it make sense for FETCH to fetch the contents
> of the row (and subsequent requested rows) that the cursor is
> currently on *then* move, and not the other way around?

No, because WHERE CURRENT OF operates on the row last returned by FETCH,
according to the spec.  AFAICT, the conceptual model I described a
couple messages back corresponds directly to a subset of the SQL92
specification for cursors: "before first row", "on some row", and "after
last row" are all states described by the spec.  (It is a subset because
we don't support DELETE WHERE CURRENT OF --- if we did, we'd need
additional states to handle the situation where the cursor is pointing
at a just-deleted row.)

We don't get to define our own behavior for FETCH.

> Additionally, with that model, UPDATE WHERE CURRENT OF would work
> exactly as you'd expect in all situations: it would update the row the
> cursor is on or, if the cursor is beyond the last row, would fail.

How do you figure that?  It would operate on the row after the last one
you'd seen, which seems pretty darn bizarre (ie, useless and dangerous)
to me.
        regards, tom lane


Re: MOVE strangeness

From
Kevin Brown
Date:
Tom Lane wrote:
> > Okay.  But then doesn't it make sense for FETCH to fetch the contents
> > of the row (and subsequent requested rows) that the cursor is
> > currently on *then* move, and not the other way around?
> 
> No, because WHERE CURRENT OF operates on the row last returned by FETCH,
> according to the spec.  AFAICT, the conceptual model I described a
> couple messages back corresponds directly to a subset of the SQL92
> specification for cursors: "before first row", "on some row", and "after
> last row" are all states described by the spec.  (It is a subset because
> we don't support DELETE WHERE CURRENT OF --- if we did, we'd need
> additional states to handle the situation where the cursor is pointing
> at a just-deleted row.)
> 
> We don't get to define our own behavior for FETCH.

I know.  I just think it's unfortunate that the spec was written the
way it was, because the behavior the spec calls for is not what I
think most people would expect of a cursor.

When I think of a cursor's behavior, what I would normally expect is
for it to behave the way my editor's cursor behaves.  The cursor is
either on a character or on the position after the last character.
When I type in insert mode, the characters I type are inserted before
the character the cursor is on.  When I type in overstrike ("update")
mode, the character the cursor is on is replaced by the character I
typed and the cursor is moved forward one position.  When I delete the
character the cursor is on (using the DEL key), the character the
cursor is on is deleted and the character that followed now becomes
the character the cursor sits on.  When I backspace, the character
before the cursor is deleted and the cursor remains on the same
character it was on prior to the backspace operation.

Some editors use a very thin cursor that is always placed between
characters, but the resulting semantics are basically the same as
described above.

This model is extremely common and, IMO, sensible.  If SQL cursors
were implemented with the same semantics, then UPDATE WHERE CURRENT OF
would update the row the cursor currently sits on, then (perhaps) move
the cursor forward, just as you'd expect (since it's the equivalent of
typing a character in overstrike mode).  INSERT WHERE CURRENT OF would
insert a new row before the row the cursor is on (so to insert a row
at the end you'd have to move the cursor past the last row) and the
cursor would remain positioned on the row it was on.  DELETE WHERE
CURRENT OF would, of course, delete the number of rows requested,
starting with the row the cursor is on, and position the first
remaining row after the deleted set underneath the cursor.  There
would be no "cursor points to the deleted row" because such a thing
makes no sense and doesn't have any real use that I'm aware of
(correct me if I'm wrong, please).

There's no need to have a "before the first row" position *and* an
"after the last row" position, since INSERT must always insert either
before the current row or after the current row (before if you use the
same semantics I described above).  


None of this matters, of course, because the SQL spec calls for a
completely different behavior.

My question is: *why* does it call for such a completely different
behavior?  Does anyone here have any insight into that?



-- 
Kevin Brown                          kevin@sysexperts.com


Re: MOVE strangeness

From
Manfred Koizar
Date:
On Sun, 29 Dec 2002 16:39:37 -0800, Kevin Brown <kevin@sysexperts.com>
wrote:
>> > Okay.  But then doesn't it make sense for FETCH to fetch the contents
>> > of the row (and subsequent requested rows) that the cursor is
>> > currently on *then* move, and not the other way around?

>This model is extremely common and, IMO, sensible.  

OTOH, seeing a row *before* updating or deleting it is *extremely*
useful.  Here is what you are proposing:

SELECT * FROM t ORDER BY id;
id | txt
---+---------------1 | leave me alone2 | delete me3 | keep me

BEGIN;
DECLARE c CURSOR FOR SELECT * FROM t ORDER BY id;
FETCH 1 FROM c;
id | txt
---+---------------1 | leave me alone

-- ok, leave it alone ...

FETCH 1 FROM c;
id | txt
---+---------------2 | delete me

-- got it!  (really??)
DELETE FROM t WHERE CURRENT OF c;
COMMIT;

SELECT * FROM t ORDER BY id;
id | txt
---+---------------1 | leave me alone2 | delete me

-- oops!!

> just as you'd expect

No, not me.  Would *you* expect this?  If you really want to draw an
analogy between databases and editors, compare rows to lines, not
characters.  Now imagine an editor window just one line high, then it
is clear that after a row is brought into the window you don't want
the cursor to silently advance to the next (invisible) row.

ServusManfred