pl/pgsql: END verbosity

Started by Neil Conwayover 20 years ago61 messages
#1Neil Conway
neilc@samurai.com

In PL/PgSQL, "END LOOP" is used to terminate loop blocks, and "END IF"
is used to terminate IF blocks. This is needlessly verbose: we could
simply accept "END" in both cases without syntactic ambiguity. I'd like
to make this change, so that END can be used to terminate any kind of
block. There's no need to remove support for the present syntax, of
course, so there's no backward compatibility concern. Oracle's PL/SQL
does require "END IF" and "END LOOP", but folks interested in maximum
compatibility can always use those forms if they like.

Any objections?

-Neil

#2Josh Berkus
josh@agliodbs.com
In reply to: Neil Conway (#1)
Re: pl/pgsql: END verbosity

Neil,

In PL/PgSQL, "END LOOP" is used to terminate loop blocks, and "END IF"
is used to terminate IF blocks. This is needlessly verbose: we could
simply accept "END" in both cases without syntactic ambiguity. I'd like
to make this change, so that END can be used to terminate any kind of
block. There's no need to remove support for the present syntax, of
course, so there's no backward compatibility concern. Oracle's PL/SQL
does require "END IF" and "END LOOP", but folks interested in maximum
compatibility can always use those forms if they like.

No problem from me. Since the parser checks for block closure for all
block types, I can't see how this would be a problem.

--
--Josh

Josh Berkus
Aglio Database Solutions
San Francisco

#3Andrew Dunstan
andrew@dunslane.net
In reply to: Neil Conway (#1)
Re: pl/pgsql: END verbosity

Neil Conway said:

In PL/PgSQL, "END LOOP" is used to terminate loop blocks, and "END IF"
is used to terminate IF blocks. This is needlessly verbose: we could
simply accept "END" in both cases without syntactic ambiguity. I'd like
to make this change, so that END can be used to terminate any kind of
block. There's no need to remove support for the present syntax, of
course, so there's no backward compatibility concern. Oracle's PL/SQL
does require "END IF" and "END LOOP", but folks interested in maximum
compatibility can always use those forms if they like.

Any objections?

I'm unkeen. I see no technical advantage - it's just a matter of taste. We
advertise that plpgsql is similar to plsql - we should not do anything to
make that less so IMNSHO. Terseness is not always good, redundancy is not
always bad.

cheers

andrew

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#3)
Re: pl/pgsql: END verbosity

"Andrew Dunstan" <andrew@dunslane.net> writes:

Neil Conway said:

Any objections?

I'm unkeen. I see no technical advantage - it's just a matter of taste. We
advertise that plpgsql is similar to plsql - we should not do anything to
make that less so IMNSHO. Terseness is not always good, redundancy is not
always bad.

That was my reaction too, though I'm too tired at this hour to phrase it
so well ;-). The long-term point in my mind is that removing
syntactical redundancy always reduces the ability to detect errors or
report errors acccurately; and it may limit our freedom to introduce
new features later. Consider for example the possibility that Oracle's
next release adds some new frammish that can't be duplicated because we
chose not to distinguish various forms of "END xxx" ...

regards, tom lane

#5Neil Conway
neilc@samurai.com
In reply to: Andrew Dunstan (#3)
Re: pl/pgsql: END verbosity

Andrew Dunstan wrote:

I'm unkeen. I see no technical advantage - it's just a matter of taste.

There is no "technical advantage" to case insensitive keywords, or
dollar quoting, or a variety of other programming language features that
don't change functionality but exist to make using the programming
language easier.

We advertise that plpgsql is similar to plsql - we should not do
anything to make that less so IMNSHO.

Do you *really* mean that? This principle would mean we should reject
patches like the CONTINUE statement patch I just applied, for example,
as PL/SQL has no such construct.

In any case, I think you are overestimating the value of strict PL/SQL
compatibility. IMHO, PL/PgSQL should be a useful procedural programming
language first, and a reimplementation of PL/SQL second. We should
provide an equivalent feature (not necessarily with the same syntax) for
all of PL/SQL's useful features, but I don't see the value in copying
Oracle when PL/SQL's implementation of a feature is ugly, broken, or
inconsistent with the rest of Postgres. It's not as if complete
source-level compatibility with PL/SQL has been a goal for PL/PgSQL
anyway (and besides, there are other people, like EnterpriseDB, who can
provide that for those who need it).

Terseness is not always good, redundancy is not always bad.

Granted -- but why is redundancy a good thing here?

-Neil

#6Neil Conway
neilc@samurai.com
In reply to: Tom Lane (#4)
Re: pl/pgsql: END verbosity

Tom Lane wrote:

The long-term point in my mind is that removing syntactical
redundancy always reduces the ability to detect errors or report
errors acccurately

Lexical scoping is unambiguous in a language like PL/PgSQL. Since it is
simple to determine whether a given END matches an IF, LOOP, or BEGIN, I
don't see how it would reduce our "ability to detect errors or report
errors accurately".

Consider for example the possibility that Oracle's next release adds
some new frammish that can't be duplicated because we chose not to
distinguish various forms of "END xxx" ...

As lexical scoping is still unambiguous, we could actually add a K_LOOP
/ K_IF token to the input stream, if that would make you happier :) (Of
course I'm not suggesting this -- the point is that as far as the parser
is concerned, we should have precisely the same information for
disambiguating the input as we used to have.)

BTW, I notice that Oracle actually allows:

<<label>>
LOOP
-- ...
END LOOP label;

whereas we don't allow the optional label following END LOOP. Which goes
to my general point: this frammish has existed in PL/SQL for a while,
but it's not as if people are clamoring for us to implement it. I would
wager that most people care about having *equivalent* features to
PL/SQL, not exactly identical syntax. For example, the lack of
autonomous transactions is something people have asked for in the past,
because it *does* make porting PL/SQL applications more difficult. I
can't see anyone losing any sleep because we are slightly more relaxed
about the input we accept.

-Neil

#7Andrew Dunstan
andrew@dunslane.net
In reply to: Neil Conway (#5)
Re: pl/pgsql: END verbosity

Neil Conway said:

Andrew Dunstan wrote:

I'm unkeen. I see no technical advantage - it's just a matter of
taste.

There is no "technical advantage" to case insensitive keywords, or
dollar quoting, or a variety of other programming language features
that don't change functionality but exist to make using the
programming language easier.

But this doesn't make it easier to use - users don't just include those who
write it. The antecedent language of these, Ada, from which this syntax
comes, was explicitly designed to be reader-friendly as opposed to
writer-friendly, and this is a part of that. I can tell you from experience
of programming Ada a long time ago that I have been profoundly grateful that
this was required in the language when disentangling a badly written 1000+
line long multibranch IF statement. And I still find myself having to hunt
for what sort of block a } is closing in C, and I still find it annoying.

We advertise that plpgsql is similar to plsql - we should not do
anything to make that less so IMNSHO.

Do you *really* mean that? This principle would mean we should reject
patches like the CONTINUE statement patch I just applied, for example,
as PL/SQL has no such construct.

Well, perhaps I should have qualified that a bit - we shouldn't do it
gratuitously.

Getting the effect of CONTINUE for nested loops can be sufficiently hard
that it is arguable that implementing it is not just syntactic sugar. I seem
to recall muttering about how implementing GOTO wasn't worth the trouble.

Terseness is not always good, redundancy is not always bad.

Granted -- but why is redundancy a good thing here?

see above

cheers

andrew

#8Neil Conway
neilc@samurai.com
In reply to: Andrew Dunstan (#7)
Re: pl/pgsql: END verbosity

Andrew Dunstan wrote:

But this doesn't make it easier to use - users don't just include those who
write it. The antecedent language of these, Ada, from which this syntax
comes, was explicitly designed to be reader-friendly as opposed to
writer-friendly, and this is a part of that.

IMHO it is just needless verbiage that makes programs both harder to
read *and* harder to write, albeit marginally so. I think there is a
reason why Ada-style block terminators are in the minority among
block-structured languages :)

But obviously this is a matter of taste -- does anyone else like or
dislike the current syntax?

-Neil

#9Steve Atkins
steve@blighty.com
In reply to: Neil Conway (#8)
Re: pl/pgsql: END verbosity

On Thu, Jun 23, 2005 at 01:41:49AM +1000, Neil Conway wrote:

Andrew Dunstan wrote:

But this doesn't make it easier to use - users don't just include those who
write it. The antecedent language of these, Ada, from which this syntax
comes, was explicitly designed to be reader-friendly as opposed to
writer-friendly, and this is a part of that.

IMHO it is just needless verbiage that makes programs both harder to
read *and* harder to write, albeit marginally so. I think there is a
reason why Ada-style block terminators are in the minority among
block-structured languages :)

But obviously this is a matter of taste -- does anyone else like or
dislike the current syntax?

"Like" is a bit strong. But it does make functions written in it easier
to read. And given that the primary debugging methodolofy for pl/pgsql
is "Look at it hard and see what might be incorrect" I can't see that
as a bad thing.

I'd trade a whole lot of "harder to write" for even some "likely to
work".

Cheers,
Steve

#10Alvaro Herrera
alvherre@surnet.cl
In reply to: Steve Atkins (#9)
Re: pl/pgsql: END verbosity

On Wed, Jun 22, 2005 at 09:23:17AM -0700, Steve Atkins wrote:

On Thu, Jun 23, 2005 at 01:41:49AM +1000, Neil Conway wrote:

Andrew Dunstan wrote:

But this doesn't make it easier to use - users don't just include those who
write it. The antecedent language of these, Ada, from which this syntax
comes, was explicitly designed to be reader-friendly as opposed to
writer-friendly, and this is a part of that.

IMHO it is just needless verbiage that makes programs both harder to
read *and* harder to write, albeit marginally so. I think there is a
reason why Ada-style block terminators are in the minority among
block-structured languages :)

But obviously this is a matter of taste -- does anyone else like or
dislike the current syntax?

"Like" is a bit strong. But it does make functions written in it easier
to read. And given that the primary debugging methodolofy for pl/pgsql
is "Look at it hard and see what might be incorrect" I can't see that
as a bad thing.

Yeah, while we don't have good debugging support in pl/pgsql we
shouldn't be making it harder to read. (FWIW, yes, I think it's useful
for those keywords to be required when you have to look at homongous
functions.)

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"No renuncies a nada. No te aferres a nada."

#11Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Andrew Dunstan (#3)
Re: pl/pgsql: END verbosity

On Tue, 21 Jun 2005, Andrew Dunstan wrote:

Neil Conway said:

In PL/PgSQL, "END LOOP" is used to terminate loop blocks, and "END IF"
is used to terminate IF blocks. This is needlessly verbose: we could
simply accept "END" in both cases without syntactic ambiguity. I'd like
to make this change, so that END can be used to terminate any kind of
block. There's no need to remove support for the present syntax, of
course, so there's no backward compatibility concern. Oracle's PL/SQL
does require "END IF" and "END LOOP", but folks interested in maximum
compatibility can always use those forms if they like.

Hello,

I prefer actual syntax too, Neil. The reason isn't compatibility with
Oracle, but better readibility - it's mean more work with finishing code
but less with debugging

Regards
Pavel

#12Merlin Moncure
merlin.moncure@rcsonline.com
In reply to: Pavel Stehule (#11)
Re: pl/pgsql: END verbosity

Neil Conway said:

In PL/PgSQL, "END LOOP" is used to terminate loop blocks, and "END

IF"

is used to terminate IF blocks. This is needlessly verbose: we

could

simply accept "END" in both cases without syntactic ambiguity. I'd

like

to make this change, so that END can be used to terminate any

kind of

block. There's no need to remove support for the present syntax,

of

course, so there's no backward compatibility concern. Oracle's

PL/SQL

does require "END IF" and "END LOOP", but folks interested in

maximum

compatibility can always use those forms if they like.

Hello,

I prefer actual syntax too, Neil. The reason isn't compatibility with
Oracle, but better readibility - it's mean more work with finishing

code

but less with debugging

COBOL, which is a kissing-cousin of pl/sql, allows this. You can have a
line terminator (a period) or a specialized block terminator. Based on
my experience here I would suggest not allowing a choice. It's a famous
source of bugs.

Merlin

#13Denis Lussier
denisl@enterprisedb.com
In reply to: Alvaro Herrera (#10)
PL/pgSQL Debugger Support

Hi All,

Sorry if this isn¹t the right forum for this question... But, I noticed
that Alvaro wrote

³... we don't have good debugging support in pl/pgsql ...²

I got to thinking it¹d be kewl if PgAdmin3 supported an interactive debugger
for pl/pgsql. If anyone¹s interested in expertly tackling such a community
project, with some financial sponsorship from EDB, please contact me
privately.

--Denis Lussier
Founder & Chief Dweeb
EnterpriseDB

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Denis Lussier (#13)
Re: PL/pgSQL Debugger Support

Denis Lussier <denisl@enterprisedb.com> writes:

I got to thinking it'd be kewl if PgAdmin3 supported an interactive debugger
for pl/pgsql.

That's been kicked around before, although I don't think anyone wants to
tie it to pgAdmin specifically. Check the archives...

regards, tom lane

#15Jan Wieck
JanWieck@Yahoo.com
In reply to: Neil Conway (#6)
Re: pl/pgsql: END verbosity

On 6/22/2005 1:29 AM, Neil Conway wrote:

Tom Lane wrote:

The long-term point in my mind is that removing syntactical
redundancy always reduces the ability to detect errors or report
errors acccurately

Lexical scoping is unambiguous in a language like PL/PgSQL. Since it is
simple to determine whether a given END matches an IF, LOOP, or BEGIN, I
don't see how it would reduce our "ability to detect errors or report
errors accurately".

Consider for example the possibility that Oracle's next release adds
some new frammish that can't be duplicated because we chose not to
distinguish various forms of "END xxx" ...

As lexical scoping is still unambiguous, we could actually add a K_LOOP
/ K_IF token to the input stream, if that would make you happier :) (Of
course I'm not suggesting this -- the point is that as far as the parser
is concerned, we should have precisely the same information for
disambiguating the input as we used to have.)

BTW, I notice that Oracle actually allows:

<<label>>
LOOP
-- ...
END LOOP label;

But what if they decide to allow

LOOP
-- ...
IF condition THEN
EXIT;
END LOOP;

at some point? There you'd get ambiguity.

Jan

whereas we don't allow the optional label following END LOOP. Which goes
to my general point: this frammish has existed in PL/SQL for a while,
but it's not as if people are clamoring for us to implement it. I would
wager that most people care about having *equivalent* features to
PL/SQL, not exactly identical syntax. For example, the lack of
autonomous transactions is something people have asked for in the past,
because it *does* make porting PL/SQL applications more difficult. I
can't see anyone losing any sleep because we are slightly more relaxed
about the input we accept.

-Neil

---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

http://archives.postgresql.org

--
#======================================================================#
# It's easier to get forgiveness for being wrong than for being right. #
# Let's break this rule - forgive me. #
#================================================== JanWieck@Yahoo.com #

#16Matthew T. O'Connor
matthew@zeut.net
In reply to: Tom Lane (#14)
Re: PL/pgSQL Debugger Support

Tom Lane wrote:

Denis Lussier <denisl@enterprisedb.com> writes:

I got to thinking it'd be kewl if PgAdmin3 supported an interactive debugger
for pl/pgsql.

That's been kicked around before, although I don't think anyone wants to
tie it to pgAdmin specifically. Check the archives...

I didn't find anything relevant after a quick search, but if memory
serves, one of the objections to PgAdmin was that it was windows only.
This of course is no longer true as of PgAdmin III 1.0. It now support
Win32, Linux and FreeBSD. So perhaps that objection is no longer valid.

#17Alvaro Herrera
alvherre@surnet.cl
In reply to: Matthew T. O'Connor (#16)
Re: PL/pgSQL Debugger Support

On Thu, Jun 23, 2005 at 01:40:18PM -0400, Matthew T. O'Connor wrote:

Tom Lane wrote:

Denis Lussier <denisl@enterprisedb.com> writes:

I got to thinking it'd be kewl if PgAdmin3 supported an interactive
debugger for pl/pgsql.

That's been kicked around before, although I don't think anyone wants to
tie it to pgAdmin specifically. Check the archives...

I didn't find anything relevant after a quick search, but if memory
serves, one of the objections to PgAdmin was that it was windows only.
This of course is no longer true as of PgAdmin III 1.0. It now support
Win32, Linux and FreeBSD. So perhaps that objection is no longer valid.

I think the point is that we will have to make some modifications to
PL/pgSQL, so why not make sure we write something that any tool can use?
Say, a well-defined BE/FE protocol extension.

--
Alvaro Herrera (<alvherre[a]surnet.cl>)
"Sallah, I said NO camels! That's FIVE camels; can't you count?"
(Indiana Jones)

#18Jonah H. Harris
jharris@tvi.edu
In reply to: Alvaro Herrera (#17)
Re: PL/pgSQL Debugger Support

Alvaro,

I agree, a protocol seems to generally be the best option.

-Jonah

Alvaro Herrera wrote:

Show quoted text

On Thu, Jun 23, 2005 at 01:40:18PM -0400, Matthew T. O'Connor wrote:

Tom Lane wrote:

Denis Lussier <denisl@enterprisedb.com> writes:

I got to thinking it'd be kewl if PgAdmin3 supported an interactive
debugger for pl/pgsql.

That's been kicked around before, although I don't think anyone wants to
tie it to pgAdmin specifically. Check the archives...

I didn't find anything relevant after a quick search, but if memory
serves, one of the objections to PgAdmin was that it was windows only.
This of course is no longer true as of PgAdmin III 1.0. It now support
Win32, Linux and FreeBSD. So perhaps that objection is no longer valid.

I think the point is that we will have to make some modifications to
PL/pgSQL, so why not make sure we write something that any tool can use?
Say, a well-defined BE/FE protocol extension.

#19Josh Berkus
josh@agliodbs.com
In reply to: Denis Lussier (#13)
Re: PL/pgSQL Debugger Support

Denis, all,

I got to thinking it¹d be kewl if PgAdmin3 supported an interactive
debugger for pl/pgsql.  If anyone¹s interested in expertly tackling such a
community project, with some financial sponsorship from EDB, please contact
me privately.

Just FYI, EMS Hitech has a windows-only PL/pgSQL debugger. So it's apparently
possible even with the current tech.

Overally, though, we'd want to support something command-line like the Perl
debug shell. Then any tool could use it.

--
Josh Berkus
Aglio Database Solutions
San Francisco

#20Robert Treat
xzilla@users.sourceforge.net
In reply to: Josh Berkus (#19)
Re: PL/pgSQL Debugger Support

On Thursday 23 June 2005 14:21, Josh Berkus wrote:

Denis, all,

I got to thinking it¹d be kewl if PgAdmin3 supported an interactive
debugger for pl/pgsql.  If anyone¹s interested in expertly tackling such
a community project, with some financial sponsorship from EDB, please
contact me privately.

Just FYI, EMS Hitech has a windows-only PL/pgSQL debugger. So it's
apparently possible even with the current tech.

IIRC thier debugger is little more than BEGIN; SELECT foo(); ROLLBACK; which
isn't great as far as debuggers go.

Overally, though, we'd want to support something command-line like the Perl
debug shell. Then any tool could use it.

Uh... a lot of tools have issues executing stuff from the command line... what
we need is something sql driven, or at least that opeates at that level,
inside the db.

--
Robert Treat
Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL

#21Robert Treat
xzilla@users.sourceforge.net
In reply to: Neil Conway (#8)
Re: pl/pgsql: END verbosity

On Wednesday 22 June 2005 11:41, Neil Conway wrote:

Andrew Dunstan wrote:

But this doesn't make it easier to use - users don't just include those
who write it. The antecedent language of these, Ada, from which this
syntax comes, was explicitly designed to be reader-friendly as opposed to
writer-friendly, and this is a part of that.

IMHO it is just needless verbiage that makes programs both harder to
read *and* harder to write, albeit marginally so. I think there is a
reason why Ada-style block terminators are in the minority among
block-structured languages :)

But obviously this is a matter of taste -- does anyone else like or
dislike the current syntax?

-1 on the proposal to me... ambiguous END statements just looks like trouble
to me. I'd actually rather see you implement label...END LOOP label if you
felt like you had to change *something*.

--
Robert Treat
Build A Brighter Lamp :: Linux Apache {middleware} PostgreSQL

#22Neil Conway
neilc@samurai.com
In reply to: Jan Wieck (#15)
Re: pl/pgsql: END verbosity

Jan Wieck wrote:

But what if they decide to allow

LOOP
-- ...
IF condition THEN
EXIT;
END LOOP;

at some point? There you'd get ambiguity.

ISTM this would be ambiguous in any case:

IF condition1 THEN
foo;
IF condition2 THEN
bar;
END IF;

-Neil

#23Bob
luckyratfoot@gmail.com
In reply to: Josh Berkus (#19)
Re: PL/pgSQL Debugger Support

My understanding is that EMS Hitech is just doing client side debugging.
That is they are taking your function and creating a new process to follow
the flow of the program. So if they mess up something you may thing your
program is doing one thing when it is really doing something else. Maybe I'm
wrong here but I assume that is what is going on. While this is better than
nothing, it doesn't compare to a built in API in pl/pgsql that would allow
any tool to hook into a function and debug. Would love to work on this if I
had the low level programming skills that the main hackers have.
On 6/23/05, Josh Berkus <josh@agliodbs.com> wrote:

Show quoted text

Denis, all,

I got to thinking it¹d be kewl if PgAdmin3 supported an interactive
debugger for pl/pgsql. If anyone¹s interested in expertly tackling such

a

community project, with some financial sponsorship from EDB, please

contact

me privately.

Just FYI, EMS Hitech has a windows-only PL/pgSQL debugger. So it's
apparently
possible even with the current tech.

Overally, though, we'd want to support something command-line like the
Perl
debug shell. Then any tool could use it.

--
Josh Berkus
Aglio Database Solutions
San Francisco

---------------------------(end of broadcast)---------------------------
TIP 3: if posting/reading through Usenet, please send an appropriate
subscribe-nomail command to majordomo@postgresql.org so that your
message can get through to the mailing list cleanly

#24Peter Eisentraut
peter_e@gmx.net
In reply to: Neil Conway (#1)
Re: pl/pgsql: END verbosity

Am Mittwoch, 22. Juni 2005 04:17 schrieb Neil Conway:

In PL/PgSQL, "END LOOP" is used to terminate loop blocks, and "END IF"
is used to terminate IF blocks. This is needlessly verbose:

It is required by the SQL standard.

#25Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Peter Eisentraut (#24)
1 attachment(s)
pl/pgsql: END verbosity [patch]

Hello

this patch allows optional using label with END and END LOOP. Ending label
has only informational value, but can enhance readability large block and
enhance likeness with Oracle.

<<main>>LOOP
...
...
END LOOP<<main>>;

Regards
Pavel Stehule

Attachments:

verbose.difftext/plain; charset=US-ASCII; name=verbose.diffDownload
diff -c -r --new-file pgsql/doc/src/sgml/plpgsql.sgml pgsql.01/doc/src/sgml/plpgsql.sgml
*** pgsql/doc/src/sgml/plpgsql.sgml	2005-06-24 13:10:33.000000000 +0200
--- pgsql.01/doc/src/sgml/plpgsql.sgml	2005-06-25 15:29:27.000000000 +0200
***************
*** 456,462 ****
      <replaceable>declarations</replaceable> </optional>
  BEGIN
      <replaceable>statements</replaceable>
! END;
  </synopsis>
      </para>
  
--- 456,462 ----
      <replaceable>declarations</replaceable> </optional>
  BEGIN
      <replaceable>statements</replaceable>
! END <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>;
  </synopsis>
      </para>
  
***************
*** 1792,1798 ****
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
  
       <para>
--- 1792,1798 ----
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>;
  </synopsis>
  
       <para>
***************
*** 1923,1929 ****
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  WHILE <replaceable>expression</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
  
         <para>
--- 1923,1929 ----
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  WHILE <replaceable>expression</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>;
  </synopsis>
  
         <para>
***************
*** 2000,2006 ****
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>record_or_row</replaceable> IN <replaceable>query</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
       The record or row variable is successively assigned each row
       resulting from the <replaceable>query</replaceable> (which must be a
--- 2000,2006 ----
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>record_or_row</replaceable> IN <replaceable>query</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>;
  </synopsis>
       The record or row variable is successively assigned each row
       resulting from the <replaceable>query</replaceable> (which must be a
***************
*** 2039,2045 ****
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP 
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
       This is like the previous form, except that the source
       <command>SELECT</command> statement is specified as a string
--- 2039,2045 ----
  <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP 
      <replaceable>statements</replaceable>
! END LOOP <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>;
  </synopsis>
       This is like the previous form, except that the source
       <command>SELECT</command> statement is specified as a string
diff -c -r --new-file pgsql/src/pl/plpgsql/src/gram.y pgsql.01/src/pl/plpgsql/src/gram.y
*** pgsql/src/pl/plpgsql/src/gram.y	2005-06-24 13:11:25.000000000 +0200
--- pgsql.01/src/pl/plpgsql/src/gram.y	2005-06-25 15:21:22.000000000 +0200
***************
*** 56,61 ****
--- 56,62 ----
  											   PLpgSQL_datum *initial_datum);
  static	void			 check_sql_expr(const char *stmt);
  static	void			 plpgsql_sql_error_callback(void *arg);
+ static void 		check_labels(char *lbl, char *elbl, int lno);
  
  %}
  
***************
*** 81,86 ****
--- 82,93 ----
  			int  n_initvars;
  			int  *initvarnos;
  		}						declhdr;
+ 		struct 
+ 		{
+ 			char *label;
+ 			int lineno;
+ 			List *list;
+ 		}						loop_body;
  		List					*list;
  		PLpgSQL_type			*dtype;
  		PLpgSQL_datum			*scalar;	/* a VAR, RECFIELD, or TRIGARG */
***************
*** 122,129 ****
  %type <str>		opt_lblname opt_label
  %type <str>		opt_exitlabel
  %type <str>		execsql_start
  
! %type <list>	proc_sect proc_stmts stmt_else loop_body
  %type <stmt>	proc_stmt pl_block
  %type <stmt>	stmt_assign stmt_if stmt_loop stmt_while stmt_exit
  %type <stmt>	stmt_return stmt_return_next stmt_raise stmt_execsql
--- 129,138 ----
  %type <str>		opt_lblname opt_label
  %type <str>		opt_exitlabel
  %type <str>		execsql_start
+ %type <str>		opt_lbltext opt_endlabel
  
! %type <list>	proc_sect proc_stmts stmt_else
! %type <loop_body> loop_body
  %type <stmt>	proc_stmt pl_block
  %type <stmt>	stmt_assign stmt_if stmt_loop stmt_while stmt_exit
  %type <stmt>	stmt_return stmt_return_next stmt_raise stmt_execsql
***************
*** 248,257 ****
  				| ';'
  				;
  
! pl_block		: decl_sect K_BEGIN lno proc_sect exception_sect K_END
  					{
  						PLpgSQL_stmt_block *new;
  
  						new = palloc0(sizeof(PLpgSQL_stmt_block));
  
  						new->cmd_type	= PLPGSQL_STMT_BLOCK;
--- 257,268 ----
  				| ';'
  				;
  
! pl_block		: decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_endlabel
  					{
  						PLpgSQL_stmt_block *new;
  
+ 						check_labels($1.label, $7, $3);
+ 						
  						new = palloc0(sizeof(PLpgSQL_stmt_block));
  
  						new->cmd_type	= PLPGSQL_STMT_BLOCK;
***************
*** 788,802 ****
  						new->cmd_type = PLPGSQL_STMT_LOOP;
  						new->lineno   = $3;
  						new->label	  = $1;
! 						new->body	  = $4;
! 
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
  					}
  				;
  
! stmt_while		: opt_label K_WHILE lno expr_until_loop loop_body
  					{
  						PLpgSQL_stmt_while *new;
  
--- 799,814 ----
  						new->cmd_type = PLPGSQL_STMT_LOOP;
  						new->lineno   = $3;
  						new->label	  = $1;
! 						new->body	  = $4.list;
! 						
! 						check_labels($1, $4.label, $4.lineno);
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
  					}
  				;
  
! stmt_while		: opt_label K_WHILE lno expr_until_loop loop_body 
  					{
  						PLpgSQL_stmt_while *new;
  
***************
*** 805,812 ****
  						new->lineno   = $3;
  						new->label	  = $1;
  						new->cond	  = $4;
! 						new->body	  = $5;
  
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
--- 817,826 ----
  						new->lineno   = $3;
  						new->label	  = $1;
  						new->cond	  = $4;
! 						new->body	  = $5.list;
  
+ 						check_labels($1, $5.label, $5.lineno);
+ 							
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
***************
*** 822,828 ****
  
  							new = (PLpgSQL_stmt_fori *) $3;
  							new->label	  = $1;
! 							new->body	  = $4;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else if ($3->cmd_type == PLPGSQL_STMT_FORS)
--- 836,842 ----
  
  							new = (PLpgSQL_stmt_fori *) $3;
  							new->label	  = $1;
! 							new->body	  = $4.list;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else if ($3->cmd_type == PLPGSQL_STMT_FORS)
***************
*** 831,837 ****
  
  							new = (PLpgSQL_stmt_fors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else
--- 845,851 ----
  
  							new = (PLpgSQL_stmt_fors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4.list;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else
***************
*** 841,851 ****
  							Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
  							new = (PLpgSQL_stmt_dynfors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  
  						/* close namespace started in opt_label */
  						plpgsql_ns_pop();
  					}
  				;
--- 855,866 ----
  							Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
  							new = (PLpgSQL_stmt_dynfors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4.list;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  
  						/* close namespace started in opt_label */
+ 						check_labels($1, $4.label, $4.lineno);
  						plpgsql_ns_pop();
  					}
  				;
***************
*** 1245,1252 ****
  					}
  				;
  
! loop_body		: proc_sect K_END K_LOOP ';'
! 					{ $$ = $1; }
  				;
  
  stmt_execsql	: execsql_start lno
--- 1260,1271 ----
  					}
  				;
  
! loop_body		: proc_sect lno K_END K_LOOP opt_endlabel ';'
! 					{ 
! 						$$.list = $1;
! 						$$.lineno = $2; 
! 						$$.label = $5;	
! 					}
  				;
  
  stmt_execsql	: execsql_start lno
***************
*** 1608,1621 ****
  					}
  				;
  
  opt_exitlabel	:
  					{ $$ = NULL; }
  				| T_LABEL
  					{
- 						char	*name;
- 
- 						plpgsql_convert_ident(yytext, &name, 1);
- 						$$ = name;
  					}
  				| T_WORD
  					{
--- 1627,1645 ----
  					}
  				;
  
+ opt_endlabel :				
+ 					{
+ 						$$ = NULL;
+ 					}
+ 				|  '<' '<' opt_lbltext '>' '>'
+ 					{
+ 						$$ = $3;
+ 					}
+ 				;
  opt_exitlabel	:
  					{ $$ = NULL; }
  				| T_LABEL
  					{
  					}
  				| T_WORD
  					{
***************
*** 1623,1628 ****
--- 1647,1662 ----
  						yyerror("no such label");
  					}
  				;
+ opt_lbltext :			T_LABEL
+ 					{
+ 						char * name;
+ 						plpgsql_convert_ident(yytext, &name, 1);
+ 						$$ = name;
+ 					}
+ 				| T_WORD
+ 					{
+ 						yyerror("no such label");
+ 					}
  
  opt_exitcond	: ';'
  					{ $$ = NULL; }
***************
*** 1630,1635 ****
--- 1664,1671 ----
  					{ $$ = $2; }
  				;
  
+ 
+ 
  opt_lblname		: T_WORD
  					{
  						char	*name;
***************
*** 2210,2213 ****
--- 2246,2267 ----
  	errposition(0);
  }
  
+ static void 
+ check_labels(char *lbl, char *elbl, int lno)
+ {
+ 	if (elbl)
+ 	{
+ 		if (lbl == NULL)
+ 		{
+ 			plpgsql_error_lineno = lno;
+ 			yyerror("Can't to specify end label without begin label.");
+ 		}
+ 		if (strcmp(lbl, elbl) != 0)
+ 		{
+ 			plpgsql_error_lineno = lno;
+ 			yyerror("End label is defferent block/loop label");
+ 		}
+ 	}
+ }
+ 
  #include "pl_scan.c"
diff -c -r --new-file pgsql/src/test/regress/expected/plpgsql.out pgsql.01/src/test/regress/expected/plpgsql.out
*** pgsql/src/test/regress/expected/plpgsql.out	2005-06-24 13:11:38.000000000 +0200
--- pgsql.01/src/test/regress/expected/plpgsql.out	2005-06-25 15:23:17.000000000 +0200
***************
*** 2666,2668 ****
--- 2666,2711 ----
  drop function continue_test2();
  drop function continue_test3();
  drop table conttesttbl;
+ -- verbose end block and end loop
+ create function vfoo() returns void as $$
+ <<blbl>>
+ begin
+   <<flbl1>>
+   for _i in 1 .. 10 loop
+     exit flbl1;
+   end loop <<flbl1>>;
+   <<flbl2>>
+   for _i in 1 .. 10 loop
+     exit flbl2;
+   end loop;
+ end <<blbl>>;
+ $$ language plpgsql;  
+ CREATE FUNCTION
+ create function vfoo2() returns void as $$
+ <<blbl>>
+ begin
+   for _i in 1 .. 10 loop
+     exit;
+   end loop <<flbl1>>;
+   <<flbl2>>
+   for _i in 1 .. 10 loop
+     exit flbl2;
+   end loop <<flbl3>>;
+ end <<blbl>>;
+ $$ language plpgsql;  
+ ERROR:  no such label at or near "flbl1" at character 107
+ LINE 6:   end loop <<flbl1>>;
+                      ^
+ select vfoo();
+  vfoo 
+ ------
+  
+ (1 row)
+ 
+ select fvoo2();
+ ERROR:  function fvoo2() does not exist
+ HINT:  No function matches the given name and argument types. You may need to add explicit type casts.
+ drop function vfoo();
+ DROP FUNCTION
+ drop function vfoo2();
+ ERROR:  function vfoo2() does not exist
diff -c -r --new-file pgsql/src/test/regress/sql/plpgsql.sql pgsql.01/src/test/regress/sql/plpgsql.sql
*** pgsql/src/test/regress/sql/plpgsql.sql	2005-06-24 13:11:35.000000000 +0200
--- pgsql.01/src/test/regress/sql/plpgsql.sql	2005-06-25 14:10:30.000000000 +0200
***************
*** 2232,2234 ****
--- 2232,2268 ----
  drop function continue_test2();
  drop function continue_test3();
  drop table conttesttbl;
+ 
+ -- verbose end block and end loop
+ create function vfoo() returns void as $$
+ <<blbl>>
+ begin
+   <<flbl1>>
+   for _i in 1 .. 10 loop
+     exit flbl1;
+   end loop <<flbl1>>;
+   <<flbl2>>
+   for _i in 1 .. 10 loop
+     exit flbl2;
+   end loop;
+ end <<blbl>>;
+ $$ language plpgsql;  
+ 
+ create function vfoo2() returns void as $$
+ <<blbl>>
+ begin
+   for _i in 1 .. 10 loop
+     exit;
+   end loop <<flbl1>>;
+   <<flbl2>>
+   for _i in 1 .. 10 loop
+     exit flbl2;
+   end loop <<flbl3>>;
+ end <<blbl>>;
+ $$ language plpgsql;  
+ 
+ select vfoo();
+ select fvoo2();
+ 
+ drop function vfoo();
+ drop function vfoo2();
#26Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Pavel Stehule (#25)
1 attachment(s)
User's exception plpgsql

Hello

Per small recent discussion I corrected patch user's exception.

diff: User can choise any sqlstate (without class U0, which I reserve as
range for default values sqlstates - if user don't spec sqlstate, is
used value from this range). There is only basic changes in documentation
and needs enhancing. I am not able to do (I am sorry, my english is poor).

Note: patch don't create deep changes in plpgsql core. Only enhance stmts
DECLARE, RAISE and EXCEPTION condition.

Next ToDo (needs discussion): 
  + Optional message in raise stmt for user's or system exception
      raise exception division_by_zero; 
  + Possibility rethrown exception
      raise;

Regards
Pavel Stehule

Attachments:

usrexcpt.difftext/plain; charset=US-ASCII; name=usrexcpt.diffDownload
diff -c -r --new-file pgsql.01/doc/src/sgml/plpgsql.sgml pgsql.02/doc/src/sgml/plpgsql.sgml
*** pgsql.01/doc/src/sgml/plpgsql.sgml	2005-06-25 15:29:27.000000000 +0200
--- pgsql.02/doc/src/sgml/plpgsql.sgml	2005-06-25 21:56:24.000000000 +0200
***************
*** 2116,2122 ****
      <para>
       The <replaceable>condition</replaceable> names can be any of those
       shown in <xref linkend="errcodes-appendix">.  A category name matches
!      any error within its category.
       The special condition name <literal>OTHERS</>
       matches every error type except <literal>QUERY_CANCELED</>.
       (It is possible, but often unwise, to trap
--- 2116,2124 ----
      <para>
       The <replaceable>condition</replaceable> names can be any of those
       shown in <xref linkend="errcodes-appendix">.  A category name matches
!      any error within its category. You can use exception variable as
!      condition name. Exception variable is declared with type 
!      <literal>EXCEPTION</literal>
       The special condition name <literal>OTHERS</>
       matches every error type except <literal>QUERY_CANCELED</>.
       (It is possible, but often unwise, to trap
***************
*** 2570,2576 ****
      raise errors.
  
  <synopsis>
! RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional>;
  </synopsis>
  
      Possible levels are <literal>DEBUG</literal>,
--- 2572,2579 ----
      raise errors.
  
  <synopsis>
! RAISE <replaceable class="parameter">level</replaceable> 
! <optional>system exception|exception variable</optional> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional>;
  </synopsis>
  
      Possible levels are <literal>DEBUG</literal>,
***************
*** 2587,2592 ****
--- 2590,2599 ----
      variables. See <xref linkend="runtime-config"> for more
      information.
     </para>
+     
+    <para>
+    You can specify any system exception or any user exception.
+    </para>
  
     <para>
      Inside the format string, <literal>%</literal> is replaced by the
diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/gram.y pgsql.02/src/pl/plpgsql/src/gram.y
*** pgsql.01/src/pl/plpgsql/src/gram.y	2005-06-25 15:21:22.000000000 +0200
--- pgsql.02/src/pl/plpgsql/src/gram.y	2005-06-25 19:57:42.000000000 +0200
***************
*** 39,44 ****
--- 39,45 ----
  #include "plpgsql.h"
  
  #include "parser/parser.h"
+ #include "utils/elog.h"
  
  static PLpgSQL_expr		*read_sql_construct(int until,
  											int until2,
***************
*** 78,83 ****
--- 79,90 ----
  		}						forvariable;
  		struct
  		{
+ 			bool nospec;
+ 			int sqlstate;
+ 			char *refname;
+ 		}						opt_excptspec;
+ 		struct
+ 		{
  			char *label;
  			int  n_initvars;
  			int  *initvarnos;
***************
*** 103,108 ****
--- 110,116 ----
  		PLpgSQL_exception_block	*exception_block;
  		PLpgSQL_nsitem			*nsitem;
  		PLpgSQL_diag_item		*diagitem;
+ 		PLpgSQL_usrexcpt		*usrexcpt;
  }
  
  %type <declhdr> decl_sect
***************
*** 115,125 ****
--- 123,135 ----
  %type <list>	decl_cursor_arglist
  %type <nsitem>	decl_aliasitem
  %type <str>		decl_stmts decl_stmt
+ %type <str>	decl_defsqlstate
  
  %type <expr>	expr_until_semi expr_until_rightbracket
  %type <expr>	expr_until_then expr_until_loop
  %type <expr>	opt_exitcond
  
+ 
  %type <ival>	assign_var cursor_variable
  %type <var>		cursor_varptr
  %type <variable>	decl_cursor_arg
***************
*** 144,150 ****
  %type <exception_block> exception_sect
  %type <exception>	proc_exception
  %type <condition>	proc_conditions
! 
  
  %type <ival>	raise_level
  %type <str>		raise_msg
--- 154,161 ----
  %type <exception_block> exception_sect
  %type <exception>	proc_exception
  %type <condition>	proc_conditions
! %type <opt_excptspec>	opt_excptspec
! %type <str>	excpt_name
  
  %type <ival>	raise_level
  %type <str>		raise_msg
***************
*** 223,228 ****
--- 234,240 ----
  %token	T_LABEL
  %token	T_WORD
  %token	T_ERROR
+ %token  T_EXCEPTION
  
  %token	O_OPTION
  %token	O_DUMP
***************
*** 332,338 ****
  						PLpgSQL_variable	*var;
  
  						var = plpgsql_build_variable($1.name, $1.lineno,
! 													 $3, true);
  						if ($2)
  						{
  							if (var->dtype == PLPGSQL_DTYPE_VAR)
--- 344,351 ----
  						PLpgSQL_variable	*var;
  
  						var = plpgsql_build_variable($1.name, $1.lineno,
! 							
! 												 $3, true);
  						if ($2)
  						{
  							if (var->dtype == PLPGSQL_DTYPE_VAR)
***************
*** 361,366 ****
--- 374,408 ----
  										 errmsg("default value for row or record variable is not supported")));
  						}
  					}
+ 				| decl_varname K_EXCEPTION decl_defsqlstate
+ 					{
+ 						PLpgSQL_usrexcpt *ue;
+ 						PLpgSQL_type *dtype;
+ 						
+ 						dtype = (PLpgSQL_type *) palloc(sizeof(PLpgSQL_type));
+         					dtype->typname = "exception";
+         					dtype->ttype = PLPGSQL_TTYPE_EXCEPTION;
+     
+ 						
+ 						ue = (PLpgSQL_usrexcpt *) plpgsql_build_variable($1.name, $1.lineno,
+ 												 dtype, true);
+ 						if ($3)
+ 						{
+ 							if (strlen($3) != 5)
+ 								yyerror("Sqlstate has five chars");
+ 
+                                                 	if (strncmp($3,"U0",2) == 0)
+                                                         	ereport(ERROR,
+                             					    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                             					    errmsg("Invalid class for SQLSTATE value '%s' for user's exception.", $3),
+                             					    errhint("Class 'U0' is reserved for default values user's exceptions.")));
+  							                                                        
+                                                 	ue->sqlstate = MAKE_SQLSTATE($3[0],$3[1],$3[2],$3[3],$3[4]);
+ 						} else
+ 							ue->sqlstate = plpgsql_newUsrExceptions();
+ 						
+ 						pfree(dtype);
+ 					}
  				| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
  					{
  						plpgsql_ns_additem($4->itemtype,
***************
*** 563,568 ****
--- 605,626 ----
  				| K_DEFAULT
  				;
  
+ decl_defsqlstate	: ';'
+ 					{ $$ = NULL; }
+ 				| decl_defkey
+ 					{
+ 					    if (yylex() != T_STRING)
+                                                     ereport(ERROR,
+                                                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                          errmsg("Default value for exception type have to be string constant")));
+ 					    $$ = plpgsql_get_string_value();
+ 					    if (yylex() != ';')
+ 						    yyerror("syntax error");
+ 					    
+ 					}
+ 				;
+ 				
+ 
  proc_sect		:
  					{
  						$$ = NIL;
***************
*** 1185,1191 ****
  					}
  				;
  
! stmt_raise		: K_RAISE lno raise_level raise_msg
  					{
  						PLpgSQL_stmt_raise		*new;
  						int	tok;
--- 1243,1249 ----
  					}
  				;
  
! stmt_raise		: K_RAISE lno raise_level opt_excptspec raise_msg
  					{
  						PLpgSQL_stmt_raise		*new;
  						int	tok;
***************
*** 1195,1203 ****
  						new->cmd_type	= PLPGSQL_STMT_RAISE;
  						new->lineno		= $2;
  						new->elog_level = $3;
! 						new->message	= $4;
  						new->params		= NIL;
  
  						tok = yylex();
  
  						/*
--- 1253,1273 ----
  						new->cmd_type	= PLPGSQL_STMT_RAISE;
  						new->lineno		= $2;
  						new->elog_level = $3;
! 						new->message	= $5;
  						new->params		= NIL;
  
+                                                 if ($4.nospec == false)
+                                                 {
+                                                     new->sqlstate = $4.sqlstate;
+                                                     new->refname = $4.refname;
+                                                 }
+                                                 else
+ 						{
+                                                     new->sqlstate = (new->elog_level >= ERROR)?ERRCODE_RAISE_EXCEPTION:0;
+ 						    new->refname = NULL;
+ 						}
+ 
+ 
  						tok = yylex();
  
  						/*
***************
*** 1260,1265 ****
--- 1330,1358 ----
  					}
  				;
  
+ opt_excptspec          : T_EXCEPTION
+                                         {
+                                                 $$.nospec = false;
+                                                 $$.sqlstate = yylval.usrexcpt->sqlstate;
+                                                 $$.refname = yylval.usrexcpt->refname;
+                                         }
+                         | T_WORD
+                                         {
+ 
+                                                 PLpgSQL_condition *c = plpgsql_parse_err_condition(yytext);
+                                                 if (c->sqlerrstate == 0) /* others */
+                                                         yyerror("You have to use exception's variable or system exception");
+ 
+                                                 $$.nospec = false;
+                                                 $$.sqlstate = c->sqlerrstate;
+                                                 $$.refname = c->condname;
+                                         }
+                         | /* EMPTY */
+                                         {
+                                                 $$.nospec = true;
+                                         }
+ 
+ 
  loop_body		: proc_sect lno K_END K_LOOP opt_endlabel ';'
  					{ 
  						$$.list = $1;
***************
*** 1583,1589 ****
  					}
  				;
  
! proc_conditions	: proc_conditions K_OR opt_lblname
  						{
  							PLpgSQL_condition	*old;
  
--- 1676,1682 ----
  					}
  				;
  
! proc_conditions	: proc_conditions K_OR excpt_name
  						{
  							PLpgSQL_condition	*old;
  
***************
*** 1593,1604 ****
  
  							$$ = $1;
  						}
! 				| opt_lblname
  						{
  							$$ = plpgsql_parse_err_condition($1);
  						}
  				;
  
  expr_until_semi :
  					{ $$ = plpgsql_read_expression(';', ";"); }
  				;
--- 1686,1711 ----
  
  							$$ = $1;
  						}
! 				| excpt_name
  						{
  							$$ = plpgsql_parse_err_condition($1);
  						}
  				;
  
+ excpt_name:			T_WORD	
+ 						{
+ 							char	*name;
+ 
+ 							plpgsql_convert_ident(yytext, &name, 1);
+ 							$$ = name;
+ 						}
+ 				| T_EXCEPTION
+ 						{
+ 							$$ = yylval.usrexcpt->refname;
+ 						}
+ 				;
+ 			    
+ 
  expr_until_semi :
  					{ $$ = plpgsql_read_expression(';', ";"); }
  				;
diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/pl_comp.c pgsql.02/src/pl/plpgsql/src/pl_comp.c
*** pgsql.01/src/pl/plpgsql/src/pl_comp.c	2005-06-24 13:11:25.000000000 +0200
--- pgsql.02/src/pl/plpgsql/src/pl_comp.c	2005-06-25 20:03:14.000000000 +0200
***************
*** 80,85 ****
--- 80,87 ----
  bool		plpgsql_DumpExecTree = false;
  bool		plpgsql_check_syntax = false;
  
+ int     plpgsql_user_excpt;
+ 
  PLpgSQL_function *plpgsql_curr_compile;
  
  /* A context appropriate for short-term allocs during compilation */
***************
*** 315,320 ****
--- 317,324 ----
  	/* This is short-lived, so needn't allocate in function's cxt */
  	plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
  	datums_last = 0;
+ 	
+ 	plpgsql_user_excpt = 0;
  
  	/*
  	 * Do extra syntax checks when validating the function
***************
*** 904,910 ****
  			case PLPGSQL_NSTYPE_ROW:
  				plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]);
  				return T_ROW;
! 
  			default:
  				return T_ERROR;
  		}
--- 908,918 ----
  			case PLPGSQL_NSTYPE_ROW:
  				plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]);
  				return T_ROW;
! 				
! 			case PLPGSQL_NSTYPE_EXCEPTION:
! 				plpgsql_yylval.usrexcpt = (PLpgSQL_usrexcpt *) (plpgsql_Datums[nse->itemno]);
! 				return T_EXCEPTION;
! 				
  			default:
  				return T_ERROR;
  		}
***************
*** 1626,1631 ****
--- 1634,1658 ----
  				result = (PLpgSQL_variable *) rec;
  				break;
  			}
+                 case PLPGSQL_TTYPE_EXCEPTION:
+                         {
+                             /* Exception pseudo type */
+                             PLpgSQL_usrexcpt *excpt;
+ 
+                             excpt = palloc0(sizeof(PLpgSQL_usrexcpt));
+                             excpt->dtype = PLPGSQL_DTYPE_EXCEPTION;
+                             excpt->refname = pstrdup(refname);
+                             excpt->lineno = lineno;
+ 
+                             plpgsql_adddatum((PLpgSQL_datum *) excpt);
+                             if (add2namespace)
+                                     plpgsql_ns_additem(PLPGSQL_NSTYPE_EXCEPTION,
+                                                                     excpt->eno,
+                                                                     refname);
+                             result = (PLpgSQL_variable *) excpt;
+                             break;
+                         }
+ 
  		case PLPGSQL_TTYPE_PSEUDO:
  			ereport(ERROR,
  					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
***************
*** 1893,1898 ****
--- 1920,1928 ----
  	PLpgSQL_condition *new;
  	PLpgSQL_condition *prev;
  
+         PLpgSQL_nsitem *nse;
+         char       *cp[1];
+ 
  	/*
  	 * XXX Eventually we will want to look for user-defined exception
  	 * names here.
***************
*** 1924,1929 ****
--- 1954,1986 ----
  		}
  	}
  
+         if (!prev)
+         {
+             /* Do case conversion and word separation */
+             plpgsql_convert_ident(condname, cp, 1);
+ 
+             /*
+             * Do a lookup on the compiler's namestack
+             */
+             nse = plpgsql_ns_lookup(cp[0], NULL);
+ 
+             if (nse != NULL)
+             {
+                 PLpgSQL_usrexcpt *excpt = (PLpgSQL_usrexcpt *) (plpgsql_Datums[nse->itemno]);
+                 if (nse->itemtype == PLPGSQL_NSTYPE_EXCEPTION)
+                 {
+                     new = palloc(sizeof(PLpgSQL_condition));
+                     new->sqlerrstate = excpt->sqlstate;
+                     new->condname = condname;
+                     new->next = prev;
+                     prev = new;
+                 }
+             }
+             pfree(cp[0]);
+         }
+ 
+ 
+ 
  	if (!prev)
  		ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_OBJECT),
***************
*** 2177,2179 ****
--- 2234,2251 ----
  	if (hentry == NULL)
  		elog(WARNING, "trying to delete function that does not exist");
  }
+ 
+ #define MAX_USER_EXCPT 999
+ 
+ int
+ plpgsql_newUsrExceptions(void)
+ {
+     char rs[4];
+ 
+     if (plpgsql_user_excpt == MAX_USER_EXCPT)
+             ereport(ERROR,
+                                 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                  errmsg("Too much user's exception")));
+     sprintf(rs,"%03d", ++plpgsql_user_excpt);
+     return MAKE_SQLSTATE('U','0', rs[0],rs[1],rs[2]);
+ }
diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/pl_exec.c pgsql.02/src/pl/plpgsql/src/pl_exec.c
*** pgsql.01/src/pl/plpgsql/src/pl_exec.c	2005-06-24 13:11:25.000000000 +0200
--- pgsql.02/src/pl/plpgsql/src/pl_exec.c	2005-06-25 19:36:02.000000000 +0200
***************
*** 722,727 ****
--- 722,730 ----
  			 */
  			result = datum;
  			break;
+ 		case PLPGSQL_DTYPE_EXCEPTION:
+ 			result = NULL;
+ 			break;
  
  		default:
  			elog(ERROR, "unrecognized dtype: %d", datum->dtype);
***************
*** 825,830 ****
--- 828,834 ----
  
  			case PLPGSQL_DTYPE_RECFIELD:
  			case PLPGSQL_DTYPE_ARRAYELEM:
+ 			case PLPGSQL_DTYPE_EXCEPTION:
  				break;
  
  			default:
***************
*** 2061,2069 ****
  	 */
  	estate->err_text = raise_skip_msg;	/* suppress traceback of raise */
  
! 	ereport(stmt->elog_level,
! 	 ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
! 	  errmsg_internal("%s", plpgsql_dstring_get(&ds))));
  
  	estate->err_text = NULL;	/* un-suppress... */
  
--- 2065,2081 ----
  	 */
  	estate->err_text = raise_skip_msg;	/* suppress traceback of raise */
  
! 
!         if (stmt->refname != NULL)
!             ereport(stmt->elog_level,           /* User's exception */
!                 (errcode(stmt->sqlstate),
!                  errdetail("User's exception/notice - sqlstate: '%s', name: '%s'", unpack_sql_state(stmt->sqlstate), stmt->refname),
! 		 errhint("from RAISE stmt on line %d", stmt->lineno),
!                     errmsg_internal("%s", plpgsql_dstring_get(&ds))));
!         else
!             ereport(stmt->elog_level,
!                 (errcode(stmt->sqlstate), 
!                     errmsg_internal("%s", plpgsql_dstring_get(&ds))));
  
  	estate->err_text = NULL;	/* un-suppress... */
  
diff -c -r --new-file pgsql.01/src/pl/plpgsql/src/plpgsql.h pgsql.02/src/pl/plpgsql/src/plpgsql.h
*** pgsql.01/src/pl/plpgsql/src/plpgsql.h	2005-06-24 13:11:25.000000000 +0200
--- pgsql.02/src/pl/plpgsql/src/plpgsql.h	2005-06-25 19:27:28.000000000 +0200
***************
*** 58,64 ****
  	PLPGSQL_NSTYPE_LABEL,
  	PLPGSQL_NSTYPE_VAR,
  	PLPGSQL_NSTYPE_ROW,
! 	PLPGSQL_NSTYPE_REC
  };
  
  /* ----------
--- 58,65 ----
  	PLPGSQL_NSTYPE_LABEL,
  	PLPGSQL_NSTYPE_VAR,
  	PLPGSQL_NSTYPE_ROW,
! 	PLPGSQL_NSTYPE_REC,
! 	PLPGSQL_NSTYPE_EXCEPTION
  };
  
  /* ----------
***************
*** 73,79 ****
  	PLPGSQL_DTYPE_RECFIELD,
  	PLPGSQL_DTYPE_ARRAYELEM,
  	PLPGSQL_DTYPE_EXPR,
! 	PLPGSQL_DTYPE_TRIGARG
  };
  
  /* ----------
--- 74,81 ----
  	PLPGSQL_DTYPE_RECFIELD,
  	PLPGSQL_DTYPE_ARRAYELEM,
  	PLPGSQL_DTYPE_EXPR,
! 	PLPGSQL_DTYPE_TRIGARG,
! 	PLPGSQL_DTYPE_EXCEPTION
  };
  
  /* ----------
***************
*** 85,91 ****
  	PLPGSQL_TTYPE_SCALAR,		/* scalar types and domains */
  	PLPGSQL_TTYPE_ROW,			/* composite types */
  	PLPGSQL_TTYPE_REC,			/* RECORD pseudotype */
! 	PLPGSQL_TTYPE_PSEUDO		/* other pseudotypes */
  };
  
  /* ----------
--- 87,94 ----
  	PLPGSQL_TTYPE_SCALAR,		/* scalar types and domains */
  	PLPGSQL_TTYPE_ROW,			/* composite types */
  	PLPGSQL_TTYPE_REC,			/* RECORD pseudotype */
! 	PLPGSQL_TTYPE_PSEUDO,		/* other pseudotypes */
! 	PLPGSQL_TTYPE_EXCEPTION
  };
  
  /* ----------
***************
*** 190,195 ****
--- 193,207 ----
  	int			lineno;
  } PLpgSQL_variable;
  
+ typedef struct
+ {
+ 	int			dtype;				/* Exception variable */
+ 	int			eno;
+ 	char 	*refname;
+ 	int 	lineno;
+ 	int	sqlstate;
+ } PLpgSQL_usrexcpt;
+ 
  typedef struct PLpgSQL_expr
  {								/* SQL Query to plan and execute	*/
  	int			dtype;
***************
*** 516,521 ****
--- 528,535 ----
  	int			cmd_type;
  	int			lineno;
  	int			elog_level;
+ 	int			sqlstate;
+ 	char	   *refname;
  	char	   *message;
  	List	   *params;			/* list of expressions */
  } PLpgSQL_stmt_raise;
***************
*** 688,693 ****
--- 702,709 ----
  extern int	plpgsql_add_initdatums(int **varnos);
  extern void plpgsql_HashTableInit(void);
  extern void plpgsql_compile_error_callback(void *arg);
+ extern int plpgsql_newUsrExceptions(void);
+ 
  
  /* ----------
   * Functions in pl_handler.c
diff -c -r --new-file pgsql.01/src/test/regress/expected/plpgsql.out pgsql.02/src/test/regress/expected/plpgsql.out
*** pgsql.01/src/test/regress/expected/plpgsql.out	2005-06-25 15:23:17.000000000 +0200
--- pgsql.02/src/test/regress/expected/plpgsql.out	2005-06-25 20:34:05.000000000 +0200
***************
*** 2709,2711 ****
--- 2709,2770 ----
  DROP FUNCTION
  drop function vfoo2();
  ERROR:  function vfoo2() does not exist
+ 
+ -- user's exception
+ create function innerfx() returns integer as $$
+ declare my_excpt exception = 'U0001';
+ begin -- using msgtext as one param of exception
+   raise exception my_excpt '%', CURRENT_TIMESTAMP;
+   return 1;
+ end $$ language plpgsql;
+ ERROR:  Invalid class for SQLSTATE value 'U0001' for user's exception.
+ HINT:  Class 'U0' is reserved for default values user's exceptions.
+ CONTEXT:  compile of PL/pgSQL function "innerfx" near line 1
+ create function innerfx() returns integer as $$
+ declare my_excpt exception = 'U1001';
+ begin -- using msgtext as one param of exception
+   raise exception my_excpt '%', 100.34::numeric;
+   return 1;
+ end $$ language plpgsql;
+ CREATE FUNCTION
+ create function outerfx() returns integer as $$
+ declare 
+   my_excpt exception = 'U1001';
+   alias_div_by_zero exception = '22012';
+   my_excpt_def_sqlstate exception;
+ begin
+   begin
+     raise exception my_excpt_def_sqlstate 'foo';
+   exception when my_excpt_def_sqlstate then
+     raise notice '01 catch: %, %', sqlstate, sqlerrm;
+   end;
+   begin
+     raise notice '%', innerfx();
+   exception when my_excpt then
+     raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric;
+   end;
+   begin
+     raise exception alias_div_by_zero 'testing';
+   exception when division_by_zero then
+     raise notice 'Divison by zero: %, %', sqlstate, sqlerrm;
+   end;
+   return 1;
+ end; $$ language plpgsql;
+ CREATE FUNCTION
+ select innerfx();
+ ERROR:  100.34
+ DETAIL:  User's exception/notice - sqlstate: 'U1001', name: 'my_excpt'
+ HINT:  from RAISE stmt on line 3
+ select outerfx();
+ NOTICE:  01 catch: U0001, foo
+ NOTICE:  02 catch: U1001, 100.34
+ NOTICE:  Divison by zero: 22012, testing
+  outerfx 
+ ---------
+        1
+ (1 row)
+ 
+ drop function outerfx();
+ DROP FUNCTION
+ drop function innerfx();
+ DROP FUNCTION
diff -c -r --new-file pgsql.01/src/test/regress/sql/plpgsql.sql pgsql.02/src/test/regress/sql/plpgsql.sql
*** pgsql.01/src/test/regress/sql/plpgsql.sql	2005-06-25 14:10:30.000000000 +0200
--- pgsql.02/src/test/regress/sql/plpgsql.sql	2005-06-25 20:32:59.000000000 +0200
***************
*** 2266,2268 ****
--- 2266,2314 ----
  
  drop function vfoo();
  drop function vfoo2();
+ 
+ -- user's exception
+ create function innerfx() returns integer as $$
+ declare my_excpt exception = 'U0001';
+ begin -- using msgtext as one param of exception
+   raise exception my_excpt '%', CURRENT_TIMESTAMP;
+   return 1;
+ end $$ language plpgsql;
+ 
+ 
+ create function innerfx() returns integer as $$
+ declare my_excpt exception = 'U1001';
+ begin -- using msgtext as one param of exception
+   raise exception my_excpt '%', 100.34::numeric;
+   return 1;
+ end $$ language plpgsql;
+ 
+ create function outerfx() returns integer as $$
+ declare 
+   my_excpt exception = 'U1001';
+   alias_div_by_zero exception = '22012';
+   my_excpt_def_sqlstate exception;
+ begin
+   begin
+     raise exception my_excpt_def_sqlstate 'foo';
+   exception when my_excpt_def_sqlstate then
+     raise notice '01 catch: %, %', sqlstate, sqlerrm;
+   end;
+   begin
+     raise notice '%', innerfx();
+   exception when my_excpt then
+     raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric;
+   end;
+   begin
+     raise exception alias_div_by_zero 'testing';
+   exception when division_by_zero then
+     raise notice 'Divison by zero: %, %', sqlstate, sqlerrm;
+   end;
+   return 1;
+ end; $$ language plpgsql;
+ 
+ select innerfx();
+ select outerfx();
+ 
+ drop function outerfx();
+ drop function innerfx();
#27Neil Conway
neilc@samurai.com
In reply to: Peter Eisentraut (#24)
Re: pl/pgsql: END verbosity

Peter Eisentraut wrote:

It is required by the SQL standard.

No, it isn't -- PL/PgSQL is not defined by the SQL standard. I guess
you're referring to SQL/PSM, but that has only a passing resemblance to
PL/PgSQL. Implementing SQL/PSM in some form would definitely be worth
doing (especially now that MySQL have), but I haven't seen any plans to
do that by adapting PL/PgSQL to SQL/PSM.

In any case, there are plenty of cases in which we accept a superset of
the syntax defined by the SQL standard -- DROP TABLE { RESTRICT |
CASCADE }, for example. We have never interpreted compliance with the
SQL specification to mean that we must *only* accept the standard's
syntax and nothing else.

-Neil

#28Peter Eisentraut
peter_e@gmx.net
In reply to: Neil Conway (#27)
Re: pl/pgsql: END verbosity

Neil Conway wrote:

No, it isn't -- PL/PgSQL is not defined by the SQL standard. I guess
you're referring to SQL/PSM, but that has only a passing resemblance
to PL/PgSQL. Implementing SQL/PSM in some form would definitely be
worth doing (especially now that MySQL have), but I haven't seen any
plans to do that by adapting PL/PgSQL to SQL/PSM.

I don't claim to recall the details, but we have frequently referred to
the SQL standard when resolving issues about PL/pgSQL's syntax.

In any case, there are plenty of cases in which we accept a superset
of the syntax defined by the SQL standard -- DROP TABLE { RESTRICT |
CASCADE }, for example. We have never interpreted compliance with the
SQL specification to mean that we must *only* accept the standard's
syntax and nothing else.

The cases were we accept a superset of the SQL standard are either
additional features, backward compatibility, or compatibility to other
systems -- none of which seem to apply here.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

#29Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#27)
Re: pl/pgsql: END verbosity

On Mon, 27 Jun 2005, Neil Conway wrote:

Peter Eisentraut wrote:

It is required by the SQL standard.

No, it isn't -- PL/PgSQL is not defined by the SQL standard. I guess
you're referring to SQL/PSM, but that has only a passing resemblance to
PL/PgSQL. Implementing SQL/PSM in some form would definitely be worth
doing (especially now that MySQL have), but I haven't seen any plans to
do that by adapting PL/PgSQL to SQL/PSM.

PL/pgSQL is different language than SQL/PSM and is little bit nonsenc
adapting them to SQL/PSM. PL/SQL live still - Oracle did some enhancing,
and we can do it too.

Some parts both languages are similar and some enough different. I had
plan start develop new interpret for SQL/PSM two years ago, but I hadn't
knowleages at that time. Situation is different now. If anybody wont to
work on SQL/PSM I will go too. Solution is another pl - pl/psm. We can
adapt gram.y plpgsql to psm

Regards
Pavel Stehule

#30Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#25)
1 attachment(s)
Re: pl/pgsql: END verbosity [patch]

Pavel Stehule wrote:

this patch allows optional using label with END and END LOOP. Ending label
has only informational value, but can enhance readability large block and
enhance likeness with Oracle.

<<main>>LOOP
...
...
END LOOP<<main>>;

Attached is a revised version of this patch. Changes / comments:

- AFAICS Oracle's syntax is actually

<<label>> LOOP
...
END LOOP label;

i.e. the ending block label isn't enclosed in <<>>. I've adjusted the
patch accordingly.

- your patch broke EXIT and CONTINUE, as running the regression tests
would have made clear.

- yyerror() will set plpgsql_error_lineno, so you needn't do it
yourself. I changed it to use ereport(ERROR) anyway, as it seems a bit
more appropriate. I'm not quite happy with the error message text:

ERROR: end label "outer_label" differs from block's label "inner_label"
CONTEXT: compile of PL/pgSQL function "end_label3" near line 6

ERROR: end label "outer_label" specified for unlabelled block
CONTEXT: compile of PL/pgSQL function "end_label4" near line 5

suggestions for improvement are welcome.

BTW, I notice that some but not all the call sites of ereport(ERROR) in
PL/PgSQL's gram.y set plpgsql_error_lineno. Is there a reason for this?

Barring any objections, I'll apply the attached patch to CVS tomorrow.

-Neil

Attachments:

verbose-6.difftext/plain; name=verbose-6.diff; x-mac-creator=0; x-mac-type=0Download
Index: doc/src/sgml/plpgsql.sgml
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/doc/src/sgml/plpgsql.sgml,v
retrieving revision 1.74
diff -c -r1.74 plpgsql.sgml
*** doc/src/sgml/plpgsql.sgml	22 Jun 2005 01:35:02 -0000	1.74
--- doc/src/sgml/plpgsql.sgml	1 Jul 2005 11:43:36 -0000
***************
*** 456,462 ****
      <replaceable>declarations</replaceable> </optional>
  BEGIN
      <replaceable>statements</replaceable>
! END;
  </synopsis>
      </para>
  
--- 456,462 ----
      <replaceable>declarations</replaceable> </optional>
  BEGIN
      <replaceable>statements</replaceable>
! END <optional> <replaceable>label</replaceable> </optional>;
  </synopsis>
      </para>
  
***************
*** 1789,1806 ****
       <title><literal>LOOP</></title>
  
  <synopsis>
! <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
  
       <para>
!       <literal>LOOP</> defines an unconditional loop that is repeated indefinitely
!       until terminated by an <literal>EXIT</> or <command>RETURN</command>
!       statement.  The optional label can be used by <literal>EXIT</> statements in
!       nested loops to specify which level of nesting should be
!       terminated.
       </para>
      </sect3>
  
--- 1789,1807 ----
       <title><literal>LOOP</></title>
  
  <synopsis>
! <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
  LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional> <replaceable>label</replaceable> </optional>;
  </synopsis>
  
       <para>
!       <literal>LOOP</> defines an unconditional loop that is repeated
!       indefinitely until terminated by an <literal>EXIT</> or
!       <command>RETURN</command> statement.  The optional
!       <replaceable>label</replaceable> can be used by <literal>EXIT</>
!       and <literal>CONTINUE</literal> statements in nested loops to
!       specify which loop the statement should be applied to.
       </para>
      </sect3>
  
***************
*** 1920,1929 ****
       </indexterm>
  
  <synopsis>
! <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  WHILE <replaceable>expression</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
  
         <para>
--- 1921,1930 ----
       </indexterm>
  
  <synopsis>
! <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
  WHILE <replaceable>expression</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional> <replaceable>label</replaceable> </optional>;
  </synopsis>
  
         <para>
***************
*** 1951,1960 ****
        <title><literal>FOR</> (integer variant)</title>
  
  <synopsis>
! <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>name</replaceable> IN <optional> REVERSE </optional> <replaceable>expression</replaceable> .. <replaceable>expression</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
  
         <para>
--- 1952,1961 ----
        <title><literal>FOR</> (integer variant)</title>
  
  <synopsis>
! <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
  FOR <replaceable>name</replaceable> IN <optional> REVERSE </optional> <replaceable>expression</replaceable> .. <replaceable>expression</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional> <replaceable>labal</replaceable> </optional>;
  </synopsis>
  
         <para>
***************
*** 1997,2006 ****
       the results of a query and manipulate that data
       accordingly. The syntax is:
  <synopsis>
! <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>record_or_row</replaceable> IN <replaceable>query</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
       The record or row variable is successively assigned each row
       resulting from the <replaceable>query</replaceable> (which must be a
--- 1998,2007 ----
       the results of a query and manipulate that data
       accordingly. The syntax is:
  <synopsis>
! <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
  FOR <replaceable>record_or_row</replaceable> IN <replaceable>query</replaceable> LOOP
      <replaceable>statements</replaceable>
! END LOOP <optional> <replaceable>label</replaceable> </optional>;
  </synopsis>
       The record or row variable is successively assigned each row
       resulting from the <replaceable>query</replaceable> (which must be a
***************
*** 2036,2045 ****
       The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
       rows:
  <synopsis>
! <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
  FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP 
      <replaceable>statements</replaceable>
! END LOOP;
  </synopsis>
       This is like the previous form, except that the source
       <command>SELECT</command> statement is specified as a string
--- 2037,2046 ----
       The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
       rows:
  <synopsis>
! <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
  FOR <replaceable>record_or_row</replaceable> IN EXECUTE <replaceable>text_expression</replaceable> LOOP 
      <replaceable>statements</replaceable>
! END LOOP <optional> <replaceable>label</replaceable> </optional>;
  </synopsis>
       This is like the previous form, except that the source
       <command>SELECT</command> statement is specified as a string
Index: src/pl/plpgsql/src/gram.y
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/gram.y,v
retrieving revision 1.77
diff -c -r1.77 gram.y
*** src/pl/plpgsql/src/gram.y	22 Jun 2005 01:35:02 -0000	1.77
--- src/pl/plpgsql/src/gram.y	1 Jul 2005 12:08:45 -0000
***************
*** 56,61 ****
--- 56,63 ----
  											   PLpgSQL_datum *initial_datum);
  static	void			 check_sql_expr(const char *stmt);
  static	void			 plpgsql_sql_error_callback(void *arg);
+ static	void			 check_labels(const char *start_label,
+ 									  const char *end_label);
  
  %}
  
***************
*** 69,75 ****
  			int  lineno;
  		}						varname;
  		struct
! 		{    
  			char *name;
  			int  lineno;
  			PLpgSQL_rec     *rec;
--- 71,77 ----
  			int  lineno;
  		}						varname;
  		struct
! 		{
  			char *name;
  			int  lineno;
  			PLpgSQL_rec     *rec;
***************
*** 81,86 ****
--- 83,93 ----
  			int  n_initvars;
  			int  *initvarnos;
  		}						declhdr;
+ 		struct
+ 		{
+ 			char *end_label;
+ 			List *stmts;
+ 		}						loop_body;
  		List					*list;
  		PLpgSQL_type			*dtype;
  		PLpgSQL_datum			*scalar;	/* a VAR, RECFIELD, or TRIGARG */
***************
*** 119,129 ****
  %type <forvariable>	for_variable
  %type <stmt>	for_control
  
! %type <str>		opt_lblname opt_label
! %type <str>		opt_exitlabel
  %type <str>		execsql_start
  
! %type <list>	proc_sect proc_stmts stmt_else loop_body
  %type <stmt>	proc_stmt pl_block
  %type <stmt>	stmt_assign stmt_if stmt_loop stmt_while stmt_exit
  %type <stmt>	stmt_return stmt_return_next stmt_raise stmt_execsql
--- 126,136 ----
  %type <forvariable>	for_variable
  %type <stmt>	for_control
  
! %type <str>		opt_lblname opt_block_label opt_label
  %type <str>		execsql_start
  
! %type <list>	proc_sect proc_stmts stmt_else
! %type <loop_body>	loop_body
  %type <stmt>	proc_stmt pl_block
  %type <stmt>	stmt_assign stmt_if stmt_loop stmt_while stmt_exit
  %type <stmt>	stmt_return stmt_return_next stmt_raise stmt_execsql
***************
*** 248,257 ****
  				| ';'
  				;
  
! pl_block		: decl_sect K_BEGIN lno proc_sect exception_sect K_END
  					{
  						PLpgSQL_stmt_block *new;
  
  						new = palloc0(sizeof(PLpgSQL_stmt_block));
  
  						new->cmd_type	= PLPGSQL_STMT_BLOCK;
--- 255,266 ----
  				| ';'
  				;
  
! pl_block		: decl_sect K_BEGIN lno proc_sect exception_sect K_END opt_label
  					{
  						PLpgSQL_stmt_block *new;
  
+ 						check_labels($1.label, $7);
+ 
  						new = palloc0(sizeof(PLpgSQL_stmt_block));
  
  						new->cmd_type	= PLPGSQL_STMT_BLOCK;
***************
*** 269,275 ****
  				;
  
  
! decl_sect		: opt_label
  					{
  						plpgsql_ns_setlocal(false);
  						$$.label	  = $1;
--- 278,284 ----
  				;
  
  
! decl_sect		: opt_block_label
  					{
  						plpgsql_ns_setlocal(false);
  						$$.label	  = $1;
***************
*** 277,283 ****
  						$$.initvarnos = NULL;
  						plpgsql_add_initdatums(NULL);
  					}
! 				| opt_label decl_start
  					{
  						plpgsql_ns_setlocal(false);
  						$$.label	  = $1;
--- 286,292 ----
  						$$.initvarnos = NULL;
  						plpgsql_add_initdatums(NULL);
  					}
! 				| opt_block_label decl_start
  					{
  						plpgsql_ns_setlocal(false);
  						$$.label	  = $1;
***************
*** 285,291 ****
  						$$.initvarnos = NULL;
  						plpgsql_add_initdatums(NULL);
  					}
! 				| opt_label decl_start decl_stmts
  					{
  						plpgsql_ns_setlocal(false);
  						if ($3 != NULL)
--- 294,300 ----
  						$$.initvarnos = NULL;
  						plpgsql_add_initdatums(NULL);
  					}
! 				| opt_block_label decl_start decl_stmts
  					{
  						plpgsql_ns_setlocal(false);
  						if ($3 != NULL)
***************
*** 409,415 ****
  						plpgsql_ns_setlocal(false);
  						query = read_sql_stmt("");
  						plpgsql_ns_setlocal(true);
! 						
  						$$ = query;
  					}
  				;
--- 418,424 ----
  						plpgsql_ns_setlocal(false);
  						query = read_sql_stmt("");
  						plpgsql_ns_setlocal(true);
! 
  						$$ = query;
  					}
  				;
***************
*** 757,763 ****
  						 *	 ...							   ...
  						 * ELSE							   ELSE
  						 *	 ...							   ...
! 						 * END IF						   END IF			 
  						 *							   END IF
  						 */
  						PLpgSQL_stmt_if *new_if;
--- 766,772 ----
  						 *	 ...							   ...
  						 * ELSE							   ELSE
  						 *	 ...							   ...
! 						 * END IF						   END IF
  						 *							   END IF
  						 */
  						PLpgSQL_stmt_if *new_if;
***************
*** 776,786 ****
  
  				| K_ELSE proc_sect
  					{
! 						$$ = $2;				
  					}
  				;
  
! stmt_loop		: opt_label K_LOOP lno loop_body
  					{
  						PLpgSQL_stmt_loop *new;
  
--- 785,795 ----
  
  				| K_ELSE proc_sect
  					{
! 						$$ = $2;
  					}
  				;
  
! stmt_loop		: opt_block_label K_LOOP lno loop_body
  					{
  						PLpgSQL_stmt_loop *new;
  
***************
*** 788,802 ****
  						new->cmd_type = PLPGSQL_STMT_LOOP;
  						new->lineno   = $3;
  						new->label	  = $1;
! 						new->body	  = $4;
  
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
  					}
  				;
  
! stmt_while		: opt_label K_WHILE lno expr_until_loop loop_body
  					{
  						PLpgSQL_stmt_while *new;
  
--- 797,812 ----
  						new->cmd_type = PLPGSQL_STMT_LOOP;
  						new->lineno   = $3;
  						new->label	  = $1;
! 						new->body	  = $4.stmts;
  
+ 						check_labels($1, $4.end_label);
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
  					}
  				;
  
! stmt_while		: opt_block_label K_WHILE lno expr_until_loop loop_body
  					{
  						PLpgSQL_stmt_while *new;
  
***************
*** 805,819 ****
  						new->lineno   = $3;
  						new->label	  = $1;
  						new->cond	  = $4;
! 						new->body	  = $5;
  
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
  					}
  				;
  
! stmt_for		: opt_label K_FOR for_control loop_body
  					{
  						/* This runs after we've scanned the loop body */
  						if ($3->cmd_type == PLPGSQL_STMT_FORI)
--- 815,830 ----
  						new->lineno   = $3;
  						new->label	  = $1;
  						new->cond	  = $4;
! 						new->body	  = $5.stmts;
  
+ 						check_labels($1, $5.end_label);
  						plpgsql_ns_pop();
  
  						$$ = (PLpgSQL_stmt *)new;
  					}
  				;
  
! stmt_for		: opt_block_label K_FOR for_control loop_body
  					{
  						/* This runs after we've scanned the loop body */
  						if ($3->cmd_type == PLPGSQL_STMT_FORI)
***************
*** 822,828 ****
  
  							new = (PLpgSQL_stmt_fori *) $3;
  							new->label	  = $1;
! 							new->body	  = $4;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else if ($3->cmd_type == PLPGSQL_STMT_FORS)
--- 833,839 ----
  
  							new = (PLpgSQL_stmt_fori *) $3;
  							new->label	  = $1;
! 							new->body	  = $4.stmts;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else if ($3->cmd_type == PLPGSQL_STMT_FORS)
***************
*** 831,837 ****
  
  							new = (PLpgSQL_stmt_fors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else
--- 842,848 ----
  
  							new = (PLpgSQL_stmt_fors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4.stmts;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  						else
***************
*** 841,850 ****
  							Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
  							new = (PLpgSQL_stmt_dynfors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  
  						/* close namespace started in opt_label */
  						plpgsql_ns_pop();
  					}
--- 852,862 ----
  							Assert($3->cmd_type == PLPGSQL_STMT_DYNFORS);
  							new = (PLpgSQL_stmt_dynfors *) $3;
  							new->label	  = $1;
! 							new->body	  = $4.stmts;
  							$$ = (PLpgSQL_stmt *) new;
  						}
  
+ 						check_labels($1, $4.end_label);
  						/* close namespace started in opt_label */
  						plpgsql_ns_pop();
  					}
***************
*** 1037,1043 ****
  					}
  				;
  
! stmt_exit		: exit_type lno opt_exitlabel opt_exitcond
  					{
  						PLpgSQL_stmt_exit *new;
  
--- 1049,1055 ----
  					}
  				;
  
! stmt_exit		: exit_type lno opt_label opt_exitcond
  					{
  						PLpgSQL_stmt_exit *new;
  
***************
*** 1245,1252 ****
  					}
  				;
  
! loop_body		: proc_sect K_END K_LOOP ';'
! 					{ $$ = $1; }
  				;
  
  stmt_execsql	: execsql_start lno
--- 1257,1267 ----
  					}
  				;
  
! loop_body		: proc_sect K_END K_LOOP opt_label ';'
! 					{
! 						$$.stmts = $1;
! 						$$.end_label = $4;
! 					}
  				;
  
  stmt_execsql	: execsql_start lno
***************
*** 1262,1268 ****
  					}
  				;
  
! stmt_dynexecute : K_EXECUTE lno 
  					{
  						PLpgSQL_stmt_dynexecute *new;
  						PLpgSQL_expr *expr;
--- 1277,1283 ----
  					}
  				;
  
! stmt_dynexecute : K_EXECUTE lno
  					{
  						PLpgSQL_stmt_dynexecute *new;
  						PLpgSQL_expr *expr;
***************
*** 1418,1424 ****
  											 errmsg("cursor \"%s\" has no arguments",
  													$3->refname)));
  								}
! 								
  								if (tok != ';')
  								{
  									plpgsql_error_lineno = plpgsql_scanner_lineno();
--- 1433,1439 ----
  											 errmsg("cursor \"%s\" has no arguments",
  													$3->refname)));
  								}
! 
  								if (tok != ';')
  								{
  									plpgsql_error_lineno = plpgsql_scanner_lineno();
***************
*** 1596,1602 ****
  					{ $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
  				;
  
! opt_label		:
  					{
  						plpgsql_ns_push(NULL);
  						$$ = NULL;
--- 1611,1617 ----
  					{ $$ = plpgsql_read_expression(K_LOOP, "LOOP"); }
  				;
  
! opt_block_label	:
  					{
  						plpgsql_ns_push(NULL);
  						$$ = NULL;
***************
*** 1608,1621 ****
  					}
  				;
  
! opt_exitlabel	:
! 					{ $$ = NULL; }
  				| T_LABEL
  					{
! 						char	*name;
! 
! 						plpgsql_convert_ident(yytext, &name, 1);
! 						$$ = name;
  					}
  				| T_WORD
  					{
--- 1623,1637 ----
  					}
  				;
  
! opt_label	:
! 					{
! 						$$ = NULL;
! 					}
  				| T_LABEL
  					{
! 						char *label_name;
! 						plpgsql_convert_ident(yytext, &label_name, 1);
! 						$$ = label_name;
  					}
  				| T_WORD
  					{
***************
*** 2210,2213 ****
--- 2226,2254 ----
  	errposition(0);
  }
  
+ static void
+ check_labels(const char *start_label, const char *end_label)
+ {
+ 	if (end_label)
+ 	{
+ 		if (!start_label)
+ 		{
+ 			plpgsql_error_lineno = plpgsql_scanner_lineno();
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_SYNTAX_ERROR),
+ 					 errmsg("end label \"%s\" specified for unlabelled block",
+ 							end_label)));
+ 		}
+ 
+ 		if (strcmp(start_label, end_label) != 0)
+ 		{
+ 			plpgsql_error_lineno = plpgsql_scanner_lineno();
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_SYNTAX_ERROR),
+ 					 errmsg("end label \"%s\" differs from block's label \"%s\"",
+ 							end_label, start_label)));
+ 		}
+ 	}
+ }
+ 
  #include "pl_scan.c"
Index: src/test/regress/expected/plpgsql.out
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/expected/plpgsql.out,v
retrieving revision 1.36
diff -c -r1.36 plpgsql.out
*** src/test/regress/expected/plpgsql.out	22 Jun 2005 07:28:47 -0000	1.36
--- src/test/regress/expected/plpgsql.out	1 Jul 2005 11:53:55 -0000
***************
*** 2491,2497 ****
  (1 row)
  
  drop function raise_exprs();
! -- continue statement 
  create table conttesttbl(idx serial, v integer);
  NOTICE:  CREATE TABLE will create implicit sequence "conttesttbl_idx_seq" for serial column "conttesttbl.idx"
  insert into conttesttbl(v) values(10);
--- 2491,2497 ----
  (1 row)
  
  drop function raise_exprs();
! -- continue statement
  create table conttesttbl(idx serial, v integer);
  NOTICE:  CREATE TABLE will create implicit sequence "conttesttbl_idx_seq" for serial column "conttesttbl.idx"
  insert into conttesttbl(v) values(10);
***************
*** 2532,2538 ****
    for _i in 1..10 loop
      begin
        -- applies to outer loop, not the nested begin block
!       continue when _i < 5; 
        raise notice '%', _i;
      end;
    end loop;
--- 2532,2538 ----
    for _i in 1..10 loop
      begin
        -- applies to outer loop, not the nested begin block
!       continue when _i < 5;
        raise notice '%', _i;
      end;
    end loop;
***************
*** 2666,2668 ****
--- 2666,2723 ----
  drop function continue_test2();
  drop function continue_test3();
  drop table conttesttbl;
+ -- verbose end block and end loop
+ create function end_label1() returns void as $$
+ <<blbl>>
+ begin
+   <<flbl1>>
+   for _i in 1 .. 10 loop
+     exit flbl1;
+   end loop flbl1;
+   <<flbl2>>
+   for _i in 1 .. 10 loop
+     exit flbl2;
+   end loop;
+ end blbl;
+ $$ language plpgsql;
+ select end_label1();
+  end_label1 
+ ------------
+  
+ (1 row)
+ 
+ drop function end_label1();
+ -- should fail: undefined end label
+ create function end_label2() returns void as $$
+ begin
+   for _i in 1 .. 10 loop
+     exit;
+   end loop flbl1;
+ end;
+ $$ language plpgsql;
+ ERROR:  no such label at or near "flbl1" at character 101
+ LINE 5:   end loop flbl1;
+                    ^
+ -- should fail: end label does not match start label
+ create function end_label3() returns void as $$
+ <<outer_label>>
+ begin
+   <<inner_label>>
+   for _i in 1 .. 10 loop
+     exit;
+   end loop outer_label;
+ end;
+ $$ language plpgsql;
+ ERROR:  end label "outer_label" differs from block's label "inner_label"
+ CONTEXT:  compile of PL/pgSQL function "end_label3" near line 6
+ -- should fail: end label on a block without a start label
+ create function end_label4() returns void as $$
+ <<outer_label>>
+ begin
+   for _i in 1 .. 10 loop
+     exit;
+   end loop outer_label;
+ end;
+ $$ language plpgsql;
+ ERROR:  end label "outer_label" specified for unlabelled block
+ CONTEXT:  compile of PL/pgSQL function "end_label4" near line 5
Index: src/test/regress/sql/plpgsql.sql
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/sql/plpgsql.sql,v
retrieving revision 1.31
diff -c -r1.31 plpgsql.sql
*** src/test/regress/sql/plpgsql.sql	22 Jun 2005 07:28:47 -0000	1.31
--- src/test/regress/sql/plpgsql.sql	1 Jul 2005 11:43:36 -0000
***************
*** 2113,2119 ****
  select raise_exprs();
  drop function raise_exprs();
  
! -- continue statement 
  create table conttesttbl(idx serial, v integer);
  insert into conttesttbl(v) values(10);
  insert into conttesttbl(v) values(20);
--- 2113,2119 ----
  select raise_exprs();
  drop function raise_exprs();
  
! -- continue statement
  create table conttesttbl(idx serial, v integer);
  insert into conttesttbl(v) values(10);
  insert into conttesttbl(v) values(20);
***************
*** 2154,2160 ****
    for _i in 1..10 loop
      begin
        -- applies to outer loop, not the nested begin block
!       continue when _i < 5; 
        raise notice '%', _i;
      end;
    end loop;
--- 2154,2160 ----
    for _i in 1..10 loop
      begin
        -- applies to outer loop, not the nested begin block
!       continue when _i < 5;
        raise notice '%', _i;
      end;
    end loop;
***************
*** 2232,2234 ****
--- 2232,2282 ----
  drop function continue_test2();
  drop function continue_test3();
  drop table conttesttbl;
+ 
+ -- verbose end block and end loop
+ create function end_label1() returns void as $$
+ <<blbl>>
+ begin
+   <<flbl1>>
+   for _i in 1 .. 10 loop
+     exit flbl1;
+   end loop flbl1;
+   <<flbl2>>
+   for _i in 1 .. 10 loop
+     exit flbl2;
+   end loop;
+ end blbl;
+ $$ language plpgsql;
+ 
+ select end_label1();
+ drop function end_label1();
+ 
+ -- should fail: undefined end label
+ create function end_label2() returns void as $$
+ begin
+   for _i in 1 .. 10 loop
+     exit;
+   end loop flbl1;
+ end;
+ $$ language plpgsql;
+ 
+ -- should fail: end label does not match start label
+ create function end_label3() returns void as $$
+ <<outer_label>>
+ begin
+   <<inner_label>>
+   for _i in 1 .. 10 loop
+     exit;
+   end loop outer_label;
+ end;
+ $$ language plpgsql;
+ 
+ -- should fail: end label on a block without a start label
+ create function end_label4() returns void as $$
+ <<outer_label>>
+ begin
+   for _i in 1 .. 10 loop
+     exit;
+   end loop outer_label;
+ end;
+ $$ language plpgsql;
#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#30)
Re: pl/pgsql: END verbosity [patch]

Neil Conway <neilc@samurai.com> writes:

BTW, I notice that some but not all the call sites of ereport(ERROR) in
PL/PgSQL's gram.y set plpgsql_error_lineno. Is there a reason for this?

Without looking at the code, I think it may be that you only need to set
the variable if you want the error to point someplace different than the
last "lno" nonterminal.

regards, tom lane

#32Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#25)
Re: pl/pgsql: END verbosity [patch]

Pavel Stehule wrote:

this patch allows optional using label with END and END LOOP. Ending label
has only informational value, but can enhance readability large block and
enhance likeness with Oracle.

Reviewed and applied -- thanks for the patch.

-Neil

#33Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#26)
Re: User's exception plpgsql

Pavel Stehule wrote:

Per small recent discussion I corrected patch user's exception.

I'll review and apply this in the next day or so.

Next ToDo (needs discussion): 
+ Optional message in raise stmt for user's or system exception
raise exception division_by_zero; 
+ Possibility rethrown exception
raise;

Both sound pretty reasonable to me.

-Neil

#34Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#33)
Re: User's exception plpgsql

Neil Conway <neilc@samurai.com> writes:

Pavel Stehule wrote:

Per small recent discussion I corrected patch user's exception.

I'll review and apply this in the next day or so.

Have we got a consensus yet on the behavior? There seemed to be no
meeting of the minds at all the last time I paid attention to this
thread ...

regards, tom lane

#35Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Tom Lane (#34)
Re: User's exception plpgsql

On Mon, 4 Jul 2005, Tom Lane wrote:

Neil Conway <neilc@samurai.com> writes:

Pavel Stehule wrote:

Per small recent discussion I corrected patch user's exception.

I'll review and apply this in the next day or so.

Have we got a consensus yet on the behavior? There seemed to be no
meeting of the minds at all the last time I paid attention to this
thread ...

There was only one objection against - requirement of uniqueness, and I
corrected it.

regards
Pavel

#36Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#26)
1 attachment(s)
Re: User's exception plpgsql

Pavel Stehule wrote:

Per small recent discussion I corrected patch user's exception.

Attached is a revised patch. I haven't looked at the documentation
changes yet (more work is needed I believe) or some of the error message
text.

I was originally hoping to make "exception variables" a little more
full-featured -- it seems silly to DECLARE something that cannot be
initialized with the value of another expression, for example. I can
also see how it would be useful to evaluate an expression variable (e.g.
to print it out for debugging purposes). It would be possible extend the
operations allowed upon exception variables, thinking about this
further, I wonder if there is any point introducing the concept of an
"exception variable" in the first place. What does it buy us over simply
using a string? In other words, if we allowed the syntax:

RAISE LEVEL [ opt_sqlstate ] 'fmt' [, expr ... ]

where `opt_sqlstate' is either empty, a T_WORD we find in the table of
predefined condition names, or an expression that evaluates to a text
value. The text value must be of a certain form (e.g. 5 characters in
length, begins with a "U" and so on).

It might be slightly more difficult to parse this (especially if we
allow 'fmt' to be an expression yielding a string, not just a string
literal), but I don't think it is ambiguous and can be sorted out via
yylex().

-Neil

Attachments:

usrexcpt-11.difftext/plain; name=usrexcpt-11.diff; x-mac-creator=0; x-mac-type=0Download
Index: doc/src/sgml/plpgsql.sgml
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/doc/src/sgml/plpgsql.sgml,v
retrieving revision 1.75
diff -c -r1.75 plpgsql.sgml
*** doc/src/sgml/plpgsql.sgml	2 Jul 2005 08:59:47 -0000	1.75
--- doc/src/sgml/plpgsql.sgml	6 Jul 2005 13:26:22 -0000
***************
*** 2117,2123 ****
      <para>
       The <replaceable>condition</replaceable> names can be any of those
       shown in <xref linkend="errcodes-appendix">.  A category name matches
!      any error within its category.
       The special condition name <literal>OTHERS</>
       matches every error type except <literal>QUERY_CANCELED</>.
       (It is possible, but often unwise, to trap
--- 2117,2125 ----
      <para>
       The <replaceable>condition</replaceable> names can be any of those
       shown in <xref linkend="errcodes-appendix">.  A category name matches
!      any error within its category. You can use exception variable as
!      condition name. Exception variable is declared with type 
!      <literal>EXCEPTION</literal>
       The special condition name <literal>OTHERS</>
       matches every error type except <literal>QUERY_CANCELED</>.
       (It is possible, but often unwise, to trap
***************
*** 2571,2577 ****
      raise errors.
  
  <synopsis>
! RAISE <replaceable class="parameter">level</replaceable> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional>;
  </synopsis>
  
      Possible levels are <literal>DEBUG</literal>,
--- 2573,2580 ----
      raise errors.
  
  <synopsis>
! RAISE <replaceable class="parameter">level</replaceable> 
! <optional>system exception|exception variable</optional> '<replaceable class="parameter">format</replaceable>' <optional>, <replaceable class="parameter">expression</replaceable> <optional>, ...</optional></optional>;
  </synopsis>
  
      Possible levels are <literal>DEBUG</literal>,
***************
*** 2588,2593 ****
--- 2591,2600 ----
      variables. See <xref linkend="runtime-config"> for more
      information.
     </para>
+     
+    <para>
+    You can specify any system exception or any user exception.
+    </para>
  
     <para>
      Inside the format string, <literal>%</literal> is replaced by the
Index: src/include/utils/elog.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/include/utils/elog.h,v
retrieving revision 1.79
diff -c -r1.79 elog.h
*** src/include/utils/elog.h	10 Jun 2005 16:23:10 -0000	1.79
--- src/include/utils/elog.h	6 Jul 2005 13:26:22 -0000
***************
*** 61,66 ****
--- 61,72 ----
  	(PGSIXBIT(ch1) + (PGSIXBIT(ch2) << 6) + (PGSIXBIT(ch3) << 12) + \
  	 (PGSIXBIT(ch4) << 18) + (PGSIXBIT(ch5) << 24))
  
+ #define MAKE_SQLSTATE_STR(str) \
+ ( \
+ 	AssertMacro(strlen(str) == 5), \
+ 	MAKE_SQLSTATE(str[0], str[1], str[2], str[3], str[4])	\
+ )
+ 
  /* These macros depend on the fact that '0' becomes a zero in SIXBIT */
  #define ERRCODE_TO_CATEGORY(ec)  ((ec) & ((1 << 12) - 1))
  #define ERRCODE_IS_CATEGORY(ec)  (((ec) & ~((1 << 12) - 1)) == 0)
Index: src/pl/plpgsql/src/gram.y
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/gram.y,v
retrieving revision 1.80
diff -c -r1.80 gram.y
*** src/pl/plpgsql/src/gram.y	2 Jul 2005 17:01:59 -0000	1.80
--- src/pl/plpgsql/src/gram.y	6 Jul 2005 13:38:35 -0000
***************
*** 103,108 ****
--- 103,109 ----
  		PLpgSQL_exception_block	*exception_block;
  		PLpgSQL_nsitem			*nsitem;
  		PLpgSQL_diag_item		*diagitem;
+ 		PLpgSQL_user_exc		*user_exc;
  }
  
  %type <declhdr> decl_sect
***************
*** 142,150 ****
  %type <exception_block> exception_sect
  %type <exception>	proc_exception
  %type <condition>	proc_conditions
  
! 
! %type <ival>	raise_level
  %type <str>		raise_msg
  
  %type <list>	getdiag_list
--- 143,152 ----
  %type <exception_block> exception_sect
  %type <exception>	proc_exception
  %type <condition>	proc_conditions
+ %type <str>	    exception_name
+ %type <ival>    decl_sqlstate sqlstate_defn
  
! %type <ival>	raise_level opt_raise_exc
  %type <str>		raise_msg
  
  %type <list>	getdiag_list
***************
*** 221,226 ****
--- 223,229 ----
  %token	T_LABEL
  %token	T_WORD
  %token	T_ERROR
+ %token	T_EXCEPTION
  
  %token	O_OPTION
  %token	O_DUMP
***************
*** 348,354 ****
  										(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  										 errmsg("row or record variable cannot be NOT NULL")));
  						}
! 						if ($5 != NULL)
  						{
  							if (var->dtype == PLPGSQL_DTYPE_VAR)
  								((PLpgSQL_var *) var)->default_val = $5;
--- 351,357 ----
  										(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  										 errmsg("row or record variable cannot be NOT NULL")));
  						}
! 						if ($5)
  						{
  							if (var->dtype == PLPGSQL_DTYPE_VAR)
  								((PLpgSQL_var *) var)->default_val = $5;
***************
*** 358,363 ****
--- 361,378 ----
  										 errmsg("default value for row or record variable is not supported")));
  						}
  					}
+ 				| decl_varname K_EXCEPTION decl_sqlstate
+ 					{
+ 						PLpgSQL_user_exc *exc_var;
+ 						PLpgSQL_type dtype;
+ 
+ 						dtype.typname = "exception";
+ 						dtype.ttype = PLPGSQL_TTYPE_EXCEPTION;
+ 
+ 						exc_var = (PLpgSQL_user_exc *) plpgsql_build_variable($1.name, $1.lineno,
+ 																			  &dtype, true);
+ 						exc_var->sqlstate = $3;
+ 					}
  				| decl_varname K_ALIAS K_FOR decl_aliasitem ';'
  					{
  						plpgsql_ns_additem($4->itemtype,
***************
*** 563,572 ****
  				| K_DEFAULT
  				;
  
! proc_sect		:
  					{
! 						$$ = NIL;
  					}
  				| proc_stmts
  					{ $$ = $1; }
  				;
--- 578,615 ----
  				| K_DEFAULT
  				;
  
! decl_sqlstate	: ';'
! 					{ $$ = plpgsql_new_user_sqlstate(); }
! 				| decl_defkey sqlstate_defn ';'
  					{
!                         $$ = $2;
  					}
+ 				;
+ 
+ sqlstate_defn	: T_STRING
+ 					{
+ 						char *state_str = plpgsql_get_string_value();
+ 
+ 						if (strlen(state_str) != 5)
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 									 errmsg("invalid SQLSTATE value \"%s\" for "
+ 											"user-defined exception", state_str),
+ 									 errdetail("SQLSTATE values must be 5 characters in length")));
+ 
+ 						if (strncmp(state_str, "U0", 2) == 0)
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 									 errmsg("invalid SQLSTATE value \"%s\" for "
+ 											"user-defined exception", state_str),
+ 									 errhint("Class \"U0\" is reserved for default values user's exceptions.")));
+ 
+ 						$$ = MAKE_SQLSTATE_STR(state_str);
+ 					}
+ 				;
+ 
+ proc_sect		:
+ 					{ $$ = NIL; }
  				| proc_stmts
  					{ $$ = $1; }
  				;
***************
*** 1184,1190 ****
  					}
  				;
  
! stmt_raise		: K_RAISE lno raise_level raise_msg
  					{
  						PLpgSQL_stmt_raise		*new;
  						int	tok;
--- 1227,1233 ----
  					}
  				;
  
! stmt_raise		: K_RAISE lno raise_level opt_raise_exc raise_msg
  					{
  						PLpgSQL_stmt_raise		*new;
  						int	tok;
***************
*** 1194,1202 ****
  						new->cmd_type	= PLPGSQL_STMT_RAISE;
  						new->lineno		= $2;
  						new->elog_level = $3;
! 						new->message	= $4;
  						new->params		= NIL;
  
  						tok = yylex();
  
  						/*
--- 1237,1256 ----
  						new->cmd_type	= PLPGSQL_STMT_RAISE;
  						new->lineno		= $2;
  						new->elog_level = $3;
! 						new->message	= $5;
  						new->params		= NIL;
  
+ 						/* No exception variable or SQLSTATE value specified? */
+ 						if ($4 == -1)
+ 						{
+ 							if (new->elog_level >= ERROR)
+ 								new->sqlstate = ERRCODE_RAISE_EXCEPTION;
+ 							else
+ 								new->sqlstate = 0;
+ 						}
+ 						else
+ 							new->sqlstate = $4;
+ 
  						tok = yylex();
  
  						/*
***************
*** 1266,1271 ****
--- 1320,1344 ----
  					}
  				;
  
+ opt_raise_exc	: T_EXCEPTION
+ 					{
+ 					$$ = yylval.user_exc->sqlstate;
+ 				}
+ 				| T_WORD
+ 					{
+ 						PLpgSQL_condition *c = plpgsql_parse_err_condition(yytext);
+ 						/* Don't allow "OTHERS" to be thrown via RAISE */
+ 						if (c->sqlerrstate == 0)
+ 							yyerror("illegal SQLSTATE value");
+ 						$$ = c->sqlerrstate;
+ 						pfree(c);
+ 					}
+ 				| /* EMPTY */
+ 					{
+ 						$$ = -1;
+ 					}
+ 				;
+ 
  stmt_execsql	: execsql_start lno
  					{
  						PLpgSQL_stmt_execsql	*new;
***************
*** 1285,1292 ****
  						PLpgSQL_expr *expr;
  						int endtoken;
  
! 						expr = read_sql_construct(K_INTO, ';', "INTO|;", "SELECT ",
! 												  true, true, &endtoken);
  
  						new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
  						new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
--- 1358,1366 ----
  						PLpgSQL_expr *expr;
  						int endtoken;
  
! 						expr = read_sql_construct(K_INTO, ';', "INTO or ;",
! 												  "SELECT ", true, true,
! 												  &endtoken);
  
  						new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
  						new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
***************
*** 1581,1600 ****
  					}
  				;
  
! proc_conditions	: proc_conditions K_OR opt_lblname
! 						{
! 							PLpgSQL_condition	*old;
  
! 							for (old = $1; old->next != NULL; old = old->next)
! 								/* skip */ ;
! 							old->next = plpgsql_parse_err_condition($3);
  
! 							$$ = $1;
! 						}
! 				| opt_lblname
! 						{
! 							$$ = plpgsql_parse_err_condition($1);
! 						}
  				;
  
  expr_until_semi :
--- 1655,1686 ----
  					}
  				;
  
! proc_conditions	: proc_conditions K_OR exception_name
! 					{
! 						PLpgSQL_condition	*old;
! 
! 						for (old = $1; old->next != NULL; old = old->next)
! 							/* skip */ ;
! 						old->next = plpgsql_parse_err_condition($3);
! 						$$ = $1;
! 					}
! 				| exception_name
! 					{
! 						$$ = plpgsql_parse_err_condition($1);
! 					}
! 				;
  
! exception_name	: T_WORD
! 					{
! 						char	*name;
  
! 						plpgsql_convert_ident(yytext, &name, 1);
! 						$$ = name;
! 					}
! 				| T_EXCEPTION
! 					{
! 						$$ = yylval.user_exc->refname;
! 					}
  				;
  
  expr_until_semi :
Index: src/pl/plpgsql/src/pl_comp.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/pl_comp.c,v
retrieving revision 1.91
diff -c -r1.91 pl_comp.c
*** src/pl/plpgsql/src/pl_comp.c	10 Jun 2005 16:23:11 -0000	1.91
--- src/pl/plpgsql/src/pl_comp.c	6 Jul 2005 13:26:22 -0000
***************
*** 80,85 ****
--- 80,87 ----
  bool		plpgsql_DumpExecTree = false;
  bool		plpgsql_check_syntax = false;
  
+ static int	plpgsql_user_exc_counter;
+ 
  PLpgSQL_function *plpgsql_curr_compile;
  
  /* A context appropriate for short-term allocs during compilation */
***************
*** 316,321 ****
--- 318,325 ----
  	plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
  	datums_last = 0;
  
+ 	plpgsql_user_exc_counter = 0;
+ 
  	/*
  	 * Do extra syntax checks when validating the function
  	 * definition. We skip this when actually compiling functions for
***************
*** 905,910 ****
--- 909,918 ----
  				plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]);
  				return T_ROW;
  
+ 			case PLPGSQL_NSTYPE_EXCEPTION:
+ 				plpgsql_yylval.user_exc = (PLpgSQL_user_exc *) (plpgsql_Datums[nse->itemno]);
+ 				return T_EXCEPTION;
+ 				
  			default:
  				return T_ERROR;
  		}
***************
*** 1626,1631 ****
--- 1634,1658 ----
  				result = (PLpgSQL_variable *) rec;
  				break;
  			}
+ 		case PLPGSQL_TTYPE_EXCEPTION:
+ 			{
+ 				/* The exception type */
+ 				PLpgSQL_user_exc *exception;
+ 
+ 				exception = palloc0(sizeof(PLpgSQL_user_exc));
+ 				exception->dtype = PLPGSQL_DTYPE_EXCEPTION;
+ 				exception->refname = pstrdup(refname);
+ 				exception->lineno = lineno;
+ 				/* caller should define sqlstate! */
+ 
+ 				plpgsql_adddatum((PLpgSQL_datum *) exception);
+ 				if (add2namespace)
+ 					plpgsql_ns_additem(PLPGSQL_NSTYPE_EXCEPTION,
+ 									   exception->dno,
+ 									   refname);
+ 				result = (PLpgSQL_variable *) exception;
+ 				break;
+ 			}
  		case PLPGSQL_TTYPE_PSEUDO:
  			ereport(ERROR,
  					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
***************
*** 1883,1890 ****
   * plpgsql_parse_err_condition
   *		Generate PLpgSQL_condition entry(s) for an exception condition name
   *
!  * This has to be able to return a list because there are some duplicate
!  * names in the table of error code names.
   */
  PLpgSQL_condition *
  plpgsql_parse_err_condition(char *condname)
--- 1910,1919 ----
   * plpgsql_parse_err_condition
   *		Generate PLpgSQL_condition entry(s) for an exception condition name
   *
!  * This has to be able to return a list because there are some
!  * duplicate names in the table of error code names. Note that the
!  * exception name should already be normalized (e.g. via
!  * plpgsql_convert_ident).
   */
  PLpgSQL_condition *
  plpgsql_parse_err_condition(char *condname)
***************
*** 1892,1902 ****
  	int			i;
  	PLpgSQL_condition *new;
  	PLpgSQL_condition *prev;
! 
! 	/*
! 	 * XXX Eventually we will want to look for user-defined exception
! 	 * names here.
! 	 */
  
  	/*
  	 * OTHERS is represented as code 0 (which would map to '00000', but we
--- 1921,1927 ----
  	int			i;
  	PLpgSQL_condition *new;
  	PLpgSQL_condition *prev;
! 	PLpgSQL_nsitem *nse;
  
  	/*
  	 * OTHERS is represented as code 0 (which would map to '00000', but we
***************
*** 1924,1929 ****
--- 1949,1981 ----
  		}
  	}
  
+ 	/*
+ 	 * If "condname" is not the name of any builtin exceptions, look
+ 	 * for a user-defined exception variable with that name.
+ 	 */
+ 	if (!prev)
+ 	{
+ 		/*
+ 		 * Do a lookup on the compiler's namestack
+ 		 */
+ 		nse = plpgsql_ns_lookup(condname, NULL);
+ 
+ 		if (nse != NULL)
+ 		{
+ 			PLpgSQL_user_exc *exc_var;
+ 
+ 			exc_var = (PLpgSQL_user_exc *) (plpgsql_Datums[nse->itemno]);
+ 			if (nse->itemtype == PLPGSQL_NSTYPE_EXCEPTION)
+ 			{
+ 				new = palloc(sizeof(PLpgSQL_condition));
+ 				new->sqlerrstate = exc_var->sqlstate;
+ 				new->condname = condname;
+ 				new->next = prev;
+ 				prev = new;
+ 			}
+ 		}
+ 	}
+ 
  	if (!prev)
  		ereport(ERROR,
  				(errcode(ERRCODE_UNDEFINED_OBJECT),
***************
*** 2171,2179 ****
  	plpgsql_HashEnt *hentry;
  
  	hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
! 										   (void *) function->fn_hashkey,
  											 HASH_REMOVE,
  											 NULL);
  	if (hentry == NULL)
  		elog(WARNING, "trying to delete function that does not exist");
  }
--- 2223,2246 ----
  	plpgsql_HashEnt *hentry;
  
  	hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
! 											 (void *) function->fn_hashkey,
  											 HASH_REMOVE,
  											 NULL);
  	if (hentry == NULL)
  		elog(WARNING, "trying to delete function that does not exist");
  }
+ 
+ #define MAX_USER_EXCPT 999
+ 
+ int
+ plpgsql_new_user_sqlstate(void)
+ {
+     char str[6];
+ 
+     if (plpgsql_user_exc_counter >= MAX_USER_EXCPT)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 				 errmsg("Too many user-defined exceptions")));
+     snprintf(str, sizeof(str), "U0%03d", ++plpgsql_user_exc_counter);
+ 	return MAKE_SQLSTATE_STR(str);
+ }
Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.149
diff -c -r1.149 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c	26 Jun 2005 22:05:42 -0000	1.149
--- src/pl/plpgsql/src/pl_exec.c	6 Jul 2005 13:56:49 -0000
***************
*** 723,728 ****
--- 723,733 ----
  			result = datum;
  			break;
  
+ 		case PLPGSQL_DTYPE_EXCEPTION:
+ 			/* XXX: this is read-only at runtime -- just copy as well? */
+ 			result = NULL;
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized dtype: %d", datum->dtype);
  			result = NULL;		/* keep compiler quiet */
***************
*** 825,830 ****
--- 830,836 ----
  
  			case PLPGSQL_DTYPE_RECFIELD:
  			case PLPGSQL_DTYPE_ARRAYELEM:
+ 			case PLPGSQL_DTYPE_EXCEPTION:
  				break;
  
  			default:
***************
*** 2062,2069 ****
  	estate->err_text = raise_skip_msg;	/* suppress traceback of raise */
  
  	ereport(stmt->elog_level,
! 	 ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0,
! 	  errmsg_internal("%s", plpgsql_dstring_get(&ds))));
  
  	estate->err_text = NULL;	/* un-suppress... */
  
--- 2068,2075 ----
  	estate->err_text = raise_skip_msg;	/* suppress traceback of raise */
  
  	ereport(stmt->elog_level,
! 			(errcode(stmt->sqlstate),
! 			 errmsg_internal("%s", plpgsql_dstring_get(&ds))));
  
  	estate->err_text = NULL;	/* un-suppress... */
  
Index: src/pl/plpgsql/src/plpgsql.h
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/plpgsql.h,v
retrieving revision 1.64
diff -c -r1.64 plpgsql.h
*** src/pl/plpgsql/src/plpgsql.h	22 Jun 2005 01:35:02 -0000	1.64
--- src/pl/plpgsql/src/plpgsql.h	6 Jul 2005 13:26:22 -0000
***************
*** 58,64 ****
  	PLPGSQL_NSTYPE_LABEL,
  	PLPGSQL_NSTYPE_VAR,
  	PLPGSQL_NSTYPE_ROW,
! 	PLPGSQL_NSTYPE_REC
  };
  
  /* ----------
--- 58,65 ----
  	PLPGSQL_NSTYPE_LABEL,
  	PLPGSQL_NSTYPE_VAR,
  	PLPGSQL_NSTYPE_ROW,
! 	PLPGSQL_NSTYPE_REC,
! 	PLPGSQL_NSTYPE_EXCEPTION
  };
  
  /* ----------
***************
*** 73,79 ****
  	PLPGSQL_DTYPE_RECFIELD,
  	PLPGSQL_DTYPE_ARRAYELEM,
  	PLPGSQL_DTYPE_EXPR,
! 	PLPGSQL_DTYPE_TRIGARG
  };
  
  /* ----------
--- 74,81 ----
  	PLPGSQL_DTYPE_RECFIELD,
  	PLPGSQL_DTYPE_ARRAYELEM,
  	PLPGSQL_DTYPE_EXPR,
! 	PLPGSQL_DTYPE_TRIGARG,
! 	PLPGSQL_DTYPE_EXCEPTION
  };
  
  /* ----------
***************
*** 85,91 ****
  	PLPGSQL_TTYPE_SCALAR,		/* scalar types and domains */
  	PLPGSQL_TTYPE_ROW,			/* composite types */
  	PLPGSQL_TTYPE_REC,			/* RECORD pseudotype */
! 	PLPGSQL_TTYPE_PSEUDO		/* other pseudotypes */
  };
  
  /* ----------
--- 87,94 ----
  	PLPGSQL_TTYPE_SCALAR,		/* scalar types and domains */
  	PLPGSQL_TTYPE_ROW,			/* composite types */
  	PLPGSQL_TTYPE_REC,			/* RECORD pseudotype */
! 	PLPGSQL_TTYPE_PSEUDO,		/* other pseudotypes */
! 	PLPGSQL_TTYPE_EXCEPTION		/* user-defined exception variables */
  };
  
  /* ----------
***************
*** 173,179 ****
   * PLpgSQL_trigarg
   */
  typedef struct
! {								/* Generic datum array item		*/
  	int			dtype;
  	int			dno;
  } PLpgSQL_datum;
--- 176,182 ----
   * PLpgSQL_trigarg
   */
  typedef struct
! {								/* Generic datum array item */
  	int			dtype;
  	int			dno;
  } PLpgSQL_datum;
***************
*** 190,197 ****
  	int			lineno;
  } PLpgSQL_variable;
  
  typedef struct PLpgSQL_expr
! {								/* SQL Query to plan and execute	*/
  	int			dtype;
  	int			exprno;
  	char	   *query;
--- 193,209 ----
  	int			lineno;
  } PLpgSQL_variable;
  
+ typedef struct
+ {								/* User-defined exception variable */
+ 	int			dtype;
+ 	int			dno;
+ 	char	   *refname;
+ 	int			lineno;
+ 	int			sqlstate;
+ } PLpgSQL_user_exc;
+ 
  typedef struct PLpgSQL_expr
! {								/* SQL Query to plan and execute */
  	int			dtype;
  	int			exprno;
  	char	   *query;
***************
*** 292,298 ****
  
  
  typedef struct
! {								/* Item in the compilers namestack	*/
  	int			itemtype;
  	int			itemno;
  	char		name[1];
--- 304,310 ----
  
  
  typedef struct
! {								/* Item in the compiler's namestack	*/
  	int			itemtype;
  	int			itemno;
  	char		name[1];
***************
*** 516,521 ****
--- 528,534 ----
  	int			cmd_type;
  	int			lineno;
  	int			elog_level;
+ 	int			sqlstate;
  	char	   *message;
  	List	   *params;			/* list of expressions */
  } PLpgSQL_stmt_raise;
***************
*** 688,693 ****
--- 701,708 ----
  extern int	plpgsql_add_initdatums(int **varnos);
  extern void plpgsql_HashTableInit(void);
  extern void plpgsql_compile_error_callback(void *arg);
+ extern int	plpgsql_new_user_sqlstate(void);
+ 
  
  /* ----------
   * Functions in pl_handler.c
Index: src/test/regress/expected/plpgsql.out
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/expected/plpgsql.out,v
retrieving revision 1.38
diff -c -r1.38 plpgsql.out
*** src/test/regress/expected/plpgsql.out	2 Jul 2005 08:59:48 -0000	1.38
--- src/test/regress/expected/plpgsql.out	6 Jul 2005 15:03:56 -0000
***************
*** 2721,2723 ****
--- 2721,2778 ----
  $$ language plpgsql;
  ERROR:  end label "outer_label" specified for unlabelled block
  CONTEXT:  compile of PL/pgSQL function "end_label4" near line 5
+ -- user-defined exceptions
+ -- should fail: illegal sqlstate for exception
+ create function innerfx() returns void as $$
+ declare
+   my_exc exception = 'U0001';
+ begin -- using msgtext as one param of exception
+   raise exception my_exc '%', CURRENT_TIMESTAMP;
+ end;
+ $$ language plpgsql;
+ ERROR:  invalid SQLSTATE value "U0001" for user-defined exception
+ HINT:  Class "U0" is reserved for default values user's exceptions.
+ CONTEXT:  compile of PL/pgSQL function "innerfx" near line 2
+ create function innerfx() returns integer as $$
+ declare
+   my_exc1 exception = 'U1001';
+ begin -- using msgtext as one param of exception
+   raise exception my_exc1 '%', 100.34::numeric;
+   return 1;
+ end $$ language plpgsql;
+ create function outerfx() returns integer as $$
+ declare 
+   my_excpt exception = 'U1001';
+   alias_div_by_zero exception = '22012';
+   def_sqlstate exception;
+ begin
+   begin
+     raise exception def_sqlstate 'foo';
+   exception when def_sqlstate then
+     raise notice '01 catch: %, %', sqlstate, sqlerrm;
+   end;
+   begin
+     raise notice '%', innerfx();
+   exception when my_excpt then
+     raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric;
+   end;
+   begin
+     raise exception alias_div_by_zero 'testing';
+   exception when division_by_zero then
+     raise notice 'Divison by zero: %, %', sqlstate, sqlerrm;
+   end;
+   return 1;
+ end; $$ language plpgsql;
+ select innerfx();
+ ERROR:  100.34
+ select outerfx();
+ NOTICE:  01 catch: U0001, foo
+ NOTICE:  02 catch: U1001, 100.34
+ NOTICE:  Divison by zero: 22012, testing
+  outerfx 
+ ---------
+        1
+ (1 row)
+ 
+ drop function outerfx();
+ drop function innerfx();
Index: src/test/regress/sql/plpgsql.sql
===================================================================
RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/sql/plpgsql.sql,v
retrieving revision 1.33
diff -c -r1.33 plpgsql.sql
*** src/test/regress/sql/plpgsql.sql	2 Jul 2005 08:59:48 -0000	1.33
--- src/test/regress/sql/plpgsql.sql	6 Jul 2005 13:57:44 -0000
***************
*** 2280,2282 ****
--- 2280,2331 ----
    end loop outer_label;
  end;
  $$ language plpgsql;
+ 
+ -- user-defined exceptions
+ 
+ -- should fail: illegal sqlstate for exception
+ create function innerfx() returns void as $$
+ declare
+   my_exc exception = 'U0001';
+ begin -- using msgtext as one param of exception
+   raise exception my_exc '%', CURRENT_TIMESTAMP;
+ end;
+ $$ language plpgsql;
+ 
+ create function innerfx() returns integer as $$
+ declare
+   my_exc1 exception = 'U1001';
+ begin -- using msgtext as one param of exception
+   raise exception my_exc1 '%', 100.34::numeric;
+   return 1;
+ end $$ language plpgsql;
+ 
+ create function outerfx() returns integer as $$
+ declare 
+   my_excpt exception = 'U1001';
+   alias_div_by_zero exception = '22012';
+   def_sqlstate exception;
+ begin
+   begin
+     raise exception def_sqlstate 'foo';
+   exception when def_sqlstate then
+     raise notice '01 catch: %, %', sqlstate, sqlerrm;
+   end;
+   begin
+     raise notice '%', innerfx();
+   exception when my_excpt then
+     raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric;
+   end;
+   begin
+     raise exception alias_div_by_zero 'testing';
+   exception when division_by_zero then
+     raise notice 'Divison by zero: %, %', sqlstate, sqlerrm;
+   end;
+   return 1;
+ end; $$ language plpgsql;
+ 
+ select innerfx();
+ select outerfx();
+ 
+ drop function outerfx();
+ drop function innerfx();
#37Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#36)
Re: User's exception plpgsql

Neil Conway <neilc@samurai.com> writes:

I wonder if there is any point introducing the concept of an
"exception variable" in the first place. What does it buy us over simply
using a string?

Not a lot really, except for keeping things similar to the Oracle way of
doing it ... but that's a nontrivial consideration.

RAISE LEVEL [ opt_sqlstate ] 'fmt' [, expr ... ]

It might be slightly more difficult to parse this (especially if we
allow 'fmt' to be an expression yielding a string, not just a string
literal), but I don't think it is ambiguous and can be sorted out via
yylex().

I think it is a bad idea, if not actually impossible, to have an
expression for sqlstate with no separating syntax before the 'fmt';
especially not if you'd like to also allow an expression for the 'fmt'.

At one point we had talked about

RAISE LEVEL [ opt_sqlstate, ] 'fmt' [, expr ... ]

The hard part here is that there isn't any very easy way to tell whether
you have a sqlstate, a fmt, and N exprs, or a fmt and N+1 exprs. The
saving grace of the declared-exception approach for this is that you
can tell by the datatype of the first argument expression which case you
have: if the expression yields text, it's a fmt, if it yields "exception"
(which we assume is an actual datatype) then it's a sqlstate.

We could handle "undeclared exceptions" in such a design by having a
function that converts text to an exception value:

RAISE LEVEL SQLSTATE('12345'), 'format here', ...

and maybe the short-term cheesy thing to do is special-case exactly this
syntax:

RAISE LEVEL [ SQLSTATE(text_expr), ] text_expr [, ... ]

which would give us the minimum functionality with a clear path to
expansion later.

regards, tom lane

#38Neil Conway
neilc@samurai.com
In reply to: Tom Lane (#37)
Re: User's exception plpgsql

Tom Lane wrote:

I think it is a bad idea, if not actually impossible, to have an
expression for sqlstate with no separating syntax before the 'fmt';
especially not if you'd like to also allow an expression for the 'fmt'.

I don't actually see much of a need to allow 'fmt' to be an expression,
especially now that RAISE parameters can be expressions. The ratio of
constant printf() format strings to variable format strings is probably
100:1, for good reason...

The hard part here is that there isn't any very easy way to tell whether
you have a sqlstate, a fmt, and N exprs, or a fmt and N+1 exprs. The
saving grace of the declared-exception approach for this is that you
can tell by the datatype of the first argument expression which case you
have

I really don't like the idea of introducing a new concept into the
language ("exception variables") to resolve some ambiguous syntax. It
would be another matter if exception variables actually provided
something that strings do not...

Another solution might be varying the syntax slightly, such as:

RAISE [ opt_sqlstate ] LEVEL 'fmt' [ , expr ... ]

-Neil

#39Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#36)
Re: User's exception plpgsql

"exception variable" in the first place. What does it buy us over simply
using a string? In other words, if we allowed the syntax:

RAISE LEVEL [ opt_sqlstate ] 'fmt' [, expr ... ]

where `opt_sqlstate' is either empty, a T_WORD we find in the table of
predefined condition names, or an expression that evaluates to a text
value. The text value must be of a certain form (e.g. 5 characters in
length, begins with a "U" and so on).

I unlike this syntax. Yes, it's easy and clear, but not readable.
Exception variables are better and an way for future. SQL state can be
only one value wich can hold exception variable. And more it's more in
oracle style (I don't wont to copy all Oracle ware into PostgreSQL)

Pavel

p.s. I have patch for rethrow exception which isn't related to user's
exception (but need's finished plpgsql code). Syntax is easy, I hope

RAISE;

#40Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#38)
Re: User's exception plpgsql

Neil Conway <neilc@samurai.com> writes:

Tom Lane wrote:

I think it is a bad idea, if not actually impossible, to have an
expression for sqlstate with no separating syntax before the 'fmt';
especially not if you'd like to also allow an expression for the 'fmt'.

I don't actually see much of a need to allow 'fmt' to be an expression,

Well, in any case we have a problem if there's no comma. Consider

RAISE NOTICE '12' !! '345', ...

Is !! an infix operator (using both strings as arguments) or a postfix
operator (in which case '345' is the format)?

Another solution might be varying the syntax slightly, such as:

RAISE [ opt_sqlstate ] LEVEL 'fmt' [ , expr ... ]

This would require promoting all the options for LEVEL into fully
reserved words. You really can't get around the fact that you need
something pretty identifiable to terminate the expression.

It might work to require parentheses:

RAISE LEVEL ( sqlstate expression ), 'fmt' [, ...]

The comma after the right paren is optional from a formal point of view,
but I'd still consider it better design to use one than not. (For one
reason, it would make it much easier to catch mismatched-parens problems.)

regards, tom lane

#41Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Tom Lane (#37)
Re: User's exception plpgsql

and maybe the short-term cheesy thing to do is special-case exactly this
syntax:

RAISE LEVEL [ SQLSTATE(text_expr), ] text_expr [, ... ]

which would give us the minimum functionality with a clear path to
expansion later.

or only RAISE LEVEL SQLSTATE(text_expr)|text_expr [, ...]

if I use registered sqlstate, plpgsql knows text message. But I think this
syntax has more questions than exception's variables. It's really problem
declare one exceptio's variable? It's similar like using constant
variables or magic values.

Pavel

DECLARE not_money EXCEPTION=SQLSTATE('U1101');
BEGIN
IF account < 0 THEN
RAISE EXCEPTION not_money;
...

or

BEGIN
IF account < 0 THEN
RAISE SQLSTATE ('U1101') 'Not money';

#42Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#41)
Re: User's exception plpgsql

Pavel Stehule <stehule@kix.fsv.cvut.cz> writes:

if I use registered sqlstate, plpgsql knows text message.

No, it does not. I already pointed out that tying a single error
message to a SQLSTATE is unreasonable, because that's not how the
SQL committee intended SQLSTATEs to be used. I haven't looked at
this patch yet, but if it's doing things that way it is wrong.

regards, tom lane

#43Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Tom Lane (#40)
Re: User's exception plpgsql

This would require promoting all the options for LEVEL into fully
reserved words. You really can't get around the fact that you need
something pretty identifiable to terminate the expression.

It might work to require parentheses:

RAISE LEVEL ( sqlstate expression ), 'fmt' [, ...]

? what sense has sqlstate expression? like any expression returns sqlstate
type?
SQLSTATE('')|user exception|system exception

Pavel

#44Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Tom Lane (#42)
Re: User's exception plpgsql

On Wed, 6 Jul 2005, Tom Lane wrote:

Pavel Stehule <stehule@kix.fsv.cvut.cz> writes:

if I use registered sqlstate, plpgsql knows text message.

No, it does not. I already pointed out that tying a single error
message to a SQLSTATE is unreasonable, because that's not how the
SQL committee intended SQLSTATEs to be used. I haven't looked at
this patch yet, but if it's doing things that way it is wrong.

no, raise stmt still needs message text (patch)

regards, tom lane

What I wont. Maybe I going in wrong direction. Please, correct me. User's
exception needs and will needs message text. I don't wont to introduce
wrong programming style. But if I use exception variable and have to use
its, then there is not only SQLSTATE but there exist name of exception
too. But I wont to simplify using system's exception. The system knows all
what need: name, text, sqlstate. And in mostly time I don't wont to
substitute text of system message. But if I wont to show it, I have to
copy it.

example:

raise exception div_by_zero; -- I wont to use system message, why not?

but now, I have to do
raise exception div_by_zero, 'division by zero ...'

Regards
Pavel

#45Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#38)
Re: User's exception plpgsql

I really don't like the idea of introducing a new concept into the
language ("exception variables") to resolve some ambiguous syntax. It
would be another matter if exception variables actually provided
something that strings do not...

In this time e.variables does it - only holds sqlstate and name.

You see only raise stmt. But there is part of begin exception block too.
without e.v. you have to catch users exception only via OTHERS or you have
to change syntax.

EXCEPTION WHEN SQLSTATE('0000') THEN

e.v. solve this problem. And I hope so can hold others info in future

Pavel

#46Neil Conway
neilc@samurai.com
In reply to: Tom Lane (#40)
Re: User's exception plpgsql

Tom Lane wrote:

RAISE NOTICE '12' !! '345', ...

Is !! an infix operator (using both strings as arguments) or a postfix
operator (in which case '345' is the format)?

Ah, I see. I would be content to allow opt_sqlstate to be either a
string literal, a T_WORD (predefined error condition), or a TEXT
variable. If users need to throw a sqlstate that is derived from a SQL
expression, they can always assign to a TEXT variable and then specify
that variable to RAISE.

RAISE [ opt_sqlstate ] LEVEL 'fmt' [ , expr ... ]

This syntax might be slightly better anyway, as allowing two string
literals without any intervening tokens is a bit ugly. We would still
need to restrict opt_sqlstate as described above, though.

-Neil

#47Neil Conway
neilc@samurai.com
In reply to: Neil Conway (#46)
Re: User's exception plpgsql

Neil Conway wrote:

Ah, I see. I would be content to allow opt_sqlstate to be either a
string literal, a T_WORD (predefined error condition), or a TEXT
variable. If users need to throw a sqlstate that is derived from a SQL
expression, they can always assign to a TEXT variable and then specify
that variable to RAISE.

RAISE [ opt_sqlstate ] LEVEL 'fmt' [ , expr ... ]

BTW, do have we reached a consensus on this?

-Neil

#48Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#47)
Re: User's exception plpgsql

Neil Conway <neilc@samurai.com> writes:

BTW, do have we reached a consensus on this?

Doesn't look that way --- I tend to agree with you that we could avoid
inventing declared exceptions at all, but Pavel is definitely not happy
with it, and AFAIR no one else has weighed in. Maybe we need to take
the discussion back to pghackers to draw a wider audience.

regards, tom lane

#49Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#47)
Re: User's exception plpgsql

On Fri, 8 Jul 2005, Neil Conway wrote:

Neil Conway wrote:

Ah, I see. I would be content to allow opt_sqlstate to be either a
string literal, a T_WORD (predefined error condition), or a TEXT
variable. If users need to throw a sqlstate that is derived from a SQL
expression, they can always assign to a TEXT variable and then specify
that variable to RAISE.

RAISE [ opt_sqlstate ] LEVEL 'fmt' [ , expr ... ]

BTW, do have we reached a consensus on this?

-Neil

ok, but don't forget, please, on exception part.

Pavel

#50Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#49)
Re: User's exception plpgsql

Pavel Stehule wrote:

ok, but don't forget, please, on exception part.

What do you mean?

-Neil

#51Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Tom Lane (#48)
Re: User's exception plpgsql

On Fri, 8 Jul 2005, Tom Lane wrote:

Neil Conway <neilc@samurai.com> writes:

BTW, do have we reached a consensus on this?

Doesn't look that way --- I tend to agree with you that we could avoid
inventing declared exceptions at all, but Pavel is definitely not happy
with it, and AFAIR no one else has weighed in. Maybe we need to take
the discussion back to pghackers to draw a wider audience.

I am not happy (this is only half of step), but I don't expect better
discussion. My opinion is so exception variable has more possibilities,
but this solution is usefull and funkcional too. And we can introduce
exception variables later without problems if will be good time.

Discussion on pghackers was, but not too much people contributed. And
more, I don't see user's exception as big qustion this days.

Show quoted text

regards, tom lane

#52Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#50)
Re: User's exception plpgsql

On Fri, 8 Jul 2005, Neil Conway wrote:

Pavel Stehule wrote:

ok, but don't forget, please, on exception part.

What do you mean?

-Neil

BEGIN
EXCEPTION WHEN * THEN

equvalent rules for raise have to be in *

is true?

Pavel

#53Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#52)
Re: User's exception plpgsql

Pavel Stehule wrote:

BEGIN
EXCEPTION WHEN * THEN

equvalent rules for raise have to be in *

is true?

I'm sorry, but I'm still not sure what you mean.

-Neil

#54Bruce Momjian
pgman@candle.pha.pa.us
In reply to: Pavel Stehule (#52)
Re: User's exception plpgsql

Where are we on this patch? Is there something to apply?

---------------------------------------------------------------------------

Pavel Stehule wrote:

On Fri, 8 Jul 2005, Neil Conway wrote:

Pavel Stehule wrote:

ok, but don't forget, please, on exception part.

What do you mean?

-Neil

BEGIN
EXCEPTION WHEN * THEN

equvalent rules for raise have to be in *

is true?

Pavel

---------------------------(end of broadcast)---------------------------
TIP 5: don't forget to increase your free space map settings

-- 
  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, Pennsylvania 19073
#55Neil Conway
neilc@samurai.com
In reply to: Bruce Momjian (#54)
Re: User's exception plpgsql

Bruce Momjian wrote:

Where are we on this patch? Is there something to apply?

Not at the moment. I believe we have agreed that it would be better to
remove the concept of "exception variables" and just use strings, but I
haven't implemented this yet. I'm happy to do that, but I might not get
a chance till Wednesday.

-Neil

#56Neil Conway
neilc@samurai.com
In reply to: Neil Conway (#55)
Re: User's exception plpgsql

Neil Conway wrote:

Not at the moment. I believe we have agreed that it would be better to
remove the concept of "exception variables" and just use strings, but I
haven't implemented this yet.

BTW, one minor annoyance I noticed: a builtin condition name can
actually map to multiple SQLSTATE values. If we allow a builtin
condition name to be specified to RAISE, this means we'll actually need
to pass around a list of SQLSTATE values that are thrown by the RAISE,
rather than a single SQLSTATE. This seems pretty ugly, though --
especially considering that only a handful of the builtin condition
names actually do map to multiple SQLSTATEs. Does anyone have a better
suggestion?

-Neil

#57Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#56)
Re: User's exception plpgsql

On Tue, 26 Jul 2005, Neil Conway wrote:

Neil Conway wrote:

Not at the moment. I believe we have agreed that it would be better to
remove the concept of "exception variables" and just use strings, but I
haven't implemented this yet.

BTW, one minor annoyance I noticed: a builtin condition name can
actually map to multiple SQLSTATE values.

can you show sample, please?

If we allow a builtin

condition name to be specified to RAISE, this means we'll actually need
to pass around a list of SQLSTATE values that are thrown by the RAISE,
rather than a single SQLSTATE. This seems pretty ugly, though --
especially considering that only a handful of the builtin condition
names actually do map to multiple SQLSTATEs. Does anyone have a better
suggestion?

Exception variables can solve it, but its dead concept. We can have list
of prohibited condition names and for its throw compile error

condition name is ambigous

Pavel

Show quoted text

-Neil

#58Pavel Stehule
stehule@kix.fsv.cvut.cz
In reply to: Neil Conway (#56)
Re: User's exception plpgsql

hello,
sorry, exception variables don't solve this problem too. But we can detect
it in compile-time. I don't wont to complicate raise syntax.

best regards
Pavel

#59Neil Conway
neilc@samurai.com
In reply to: Pavel Stehule (#57)
Re: User's exception plpgsql

Pavel Stehule wrote:

can you show sample, please?

modifying_sql_data_not_permitted, null_value_not_allowed,
prohibited_sql_statement_attempted and reading_sql_data_not_permitted
are the examples I can see from scanning plerrcodes.h. If we had this to
do over again, I'm not sure I see the point in mapping a single
condition names to multiple SQLSTATEs, but it's probably too late to
undo that now.

Exception variables can solve it, but its dead concept. We can have list
of prohibited condition names and for its throw compile error condition
name is ambigous

Yeah, that's possible, but it doesn't seem any nicer :-(

-Neil

#60Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Conway (#59)
Re: User's exception plpgsql

Neil Conway <neilc@samurai.com> writes:

Pavel Stehule wrote:

can you show sample, please?

modifying_sql_data_not_permitted, null_value_not_allowed,
prohibited_sql_statement_attempted and reading_sql_data_not_permitted
are the examples I can see from scanning plerrcodes.h. If we had this to
do over again, I'm not sure I see the point in mapping a single
condition names to multiple SQLSTATEs, but it's probably too late to
undo that now.

Those cases are for places where the spec defines similar cases under
the error classes "SQL Routine Exception" and "External Routine Exception".
You can blame me for having assumed that plpgsql didn't need to
distinguish these cases.

A quick grep says that the only one of these codes being generated today
is
contrib/dblink/dblink.c: (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
and that's for a "you should not do that" case, which it's very unlikely
anyone is specifically trapping for. So I see no
backwards-compatibility argument that we can't change this. How would
you want to do it better?

regards, tom lane

#61Neil Conway
neilc@samurai.com
In reply to: Tom Lane (#60)
Re: User's exception plpgsql

Tom Lane wrote:

Those cases are for places where the spec defines similar cases under
the error classes "SQL Routine Exception" and "External Routine Exception".
You can blame me for having assumed that plpgsql didn't need to
distinguish these cases.

Well, in and of itself, I agree it is probably better to combine similar
SQLSTATEs into a single logical condition. However, considering the
problem it poses for implementing RAISE with builtin condition names,
IMHO it would be a net win to get rid of it, if we can't find a better
solution.

So I see no backwards-compatibility argument that we can't change
this. How would you want to do it better?

I would just change the mapping from condition names to SQLSTATEs to be
one-to-one. If a client application does need to trap multiple SQLSTATEs
for a logically similar condition, they can always specify "WHEN x OR y
OR ..."

-Neil