poll: CHECK TRIGGER?

Started by Alvaro Herreraalmost 14 years ago61 messages
#1Alvaro Herrera
alvherre@alvh.no-ip.org

Hi,

Pavel's patch for CHECK FUNCTION is adding another command besides that
one, which is CHECK TRIGGER. The idea behind this is that you give it
the relation to which the trigger is attached in addition to the trigger
name, and it checks the function being called by that trigger.

IMHO having a separate command for this is not warranted. It seems to
me that we could simply have a variant of CREATE FUNCTION for this; I
proposed CREATE FUNCTION trigfunc() AS TRIGGER ON tabname. Besides,
it's really the function code being checked, not the trigger itself; the
trigger is only providing the tuple descriptor for NEW and OLD.

Pavel didn't like this idea; also, a quick poll on twitter elicited only
two answers: Dimitri Fontaine prefers CHECK TRIGGER, and Guillaume
Lelarge prefers CHECK FUNCTION.

So I still don't know which route to go with this. Thoughts?

One thing to consider is eventual support for triggers that use
anonymous function blocks, without a previous CREATE FUNCTION, which is
being discussed in another thread. Another point is that CHECK TRIGGER
requires a separate documentation page.

--
Álvaro Herrera <alvherre@alvh.no-ip.org>

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#1)
Re: poll: CHECK TRIGGER?

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Pavel's patch for CHECK FUNCTION is adding another command besides that
one, which is CHECK TRIGGER. The idea behind this is that you give it
the relation to which the trigger is attached in addition to the trigger
name, and it checks the function being called by that trigger.

IMHO having a separate command for this is not warranted. It seems to
me that we could simply have a variant of CREATE FUNCTION for this; I
proposed CREATE FUNCTION trigfunc() AS TRIGGER ON tabname.

You mean "CHECK FUNCTION ..." right?

In principle the CHECK TRIGGER command could apply more checks than
are possible with the proposed CHECK FUNCTION syntax: in particular,
AFAICS "AS TRIGGER ON tabname" doesn't provide enough info to know
whether the function should expect new and/or old rows to be provided,
nor what it ought to return (which is different for BEFORE/AFTER cases,
STATEMENT cases, etc). We could add all that info to the CHECK FUNCTION
syntax, but there's definitely some merit to defining the check as
occurring against an existing trigger definition instead.

Now, if there's no intention of ever making the code actually apply
checks of the sort I'm thinking of, maybe CHECK FUNCTION AS TRIGGER
is fine.

One thing to consider is eventual support for triggers that use
anonymous function blocks, without a previous CREATE FUNCTION, which is
being discussed in another thread.

Yeah, that angle seems to be another reason to support CHECK TRIGGER.

regards, tom lane

#3Alvaro Herrera
alvherre@commandprompt.com
In reply to: Tom Lane (#2)
Re: poll: CHECK TRIGGER?

Excerpts from Tom Lane's message of sáb mar 03 23:00:26 -0300 2012:

Alvaro Herrera <alvherre@alvh.no-ip.org> writes:

Pavel's patch for CHECK FUNCTION is adding another command besides that
one, which is CHECK TRIGGER. The idea behind this is that you give it
the relation to which the trigger is attached in addition to the trigger
name, and it checks the function being called by that trigger.

IMHO having a separate command for this is not warranted. It seems to
me that we could simply have a variant of CREATE FUNCTION for this; I
proposed CREATE FUNCTION trigfunc() AS TRIGGER ON tabname.

You mean "CHECK FUNCTION ..." right?

Yeah, sorry.

In principle the CHECK TRIGGER command could apply more checks than
are possible with the proposed CHECK FUNCTION syntax: in particular,
AFAICS "AS TRIGGER ON tabname" doesn't provide enough info to know
whether the function should expect new and/or old rows to be provided,
nor what it ought to return (which is different for BEFORE/AFTER cases,
STATEMENT cases, etc). We could add all that info to the CHECK FUNCTION
syntax, but there's definitely some merit to defining the check as
occurring against an existing trigger definition instead.

Uh! Now that I read this I realize that what you're supposed to give to
CHECK TRIGGER is the trigger name, not the function name! In that
light, using CHECK FUNCTION for this doesn't make a lot of sense.

Okay, CHECK TRIGGER it is.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#4Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#3)
Re: poll: CHECK TRIGGER?

On Sat, Mar 3, 2012 at 9:23 PM, Alvaro Herrera
<alvherre@commandprompt.com> wrote:

Uh!  Now that I read this I realize that what you're supposed to give to
CHECK TRIGGER is the trigger name, not the function name!  In that
light, using CHECK FUNCTION for this doesn't make a lot of sense.

Okay, CHECK TRIGGER it is.

I confess to some bafflement about why we need dedicated syntax for
this, or even any kind of core support at all. What would be wrong
with defining a function that takes regprocedure as an argument and
does whatever? Sure, it's nicer syntax, but we've repeatedly rejected
patches that only provided nicer syntax on the grounds that syntax is
not free, and therefore syntax alone is not a reason to change the
core grammar. What makes this case different?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#4)
Re: poll: CHECK TRIGGER?

2012/3/5 Robert Haas <robertmhaas@gmail.com>:

On Sat, Mar 3, 2012 at 9:23 PM, Alvaro Herrera
<alvherre@commandprompt.com> wrote:

Uh!  Now that I read this I realize that what you're supposed to give to
CHECK TRIGGER is the trigger name, not the function name!  In that
light, using CHECK FUNCTION for this doesn't make a lot of sense.

Okay, CHECK TRIGGER it is.

I confess to some bafflement about why we need dedicated syntax for
this, or even any kind of core support at all.  What would be wrong
with defining a function that takes regprocedure as an argument and
does whatever?  Sure, it's nicer syntax, but we've repeatedly rejected
patches that only provided nicer syntax on the grounds that syntax is
not free, and therefore syntax alone is not a reason to change the
core grammar.  What makes this case different?

Fo checking trigger handler (trigger function) you have to know
trigger definition (only joined relation now), but it can be enhanced
for other tests based on trigger data.

Regards

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#4)
Re: poll: CHECK TRIGGER?

Robert Haas <robertmhaas@gmail.com> writes:

I confess to some bafflement about why we need dedicated syntax for
this, or even any kind of core support at all. What would be wrong
with defining a function that takes regprocedure as an argument and
does whatever? Sure, it's nicer syntax, but we've repeatedly rejected
patches that only provided nicer syntax on the grounds that syntax is
not free, and therefore syntax alone is not a reason to change the
core grammar. What makes this case different?

There's definitely something to be said for that, since it entirely
eliminates the problem of providing wildcards and control over which
function(s) to check --- the user could write a SELECT from pg_proc
that slices things however he wants.

The trigger case would presumably take arguments matching pg_trigger's
primary key, viz check_trigger(trig_rel regclass, trigger_name name).

But as for needing "core support", we do need to extend the API for PL
validators, so it's not like this could be done as an external project.

regards, tom lane

#7Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#6)
Re: poll: CHECK TRIGGER?

On Mon, Mar 5, 2012 at 11:41 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

I confess to some bafflement about why we need dedicated syntax for
this, or even any kind of core support at all.  What would be wrong
with defining a function that takes regprocedure as an argument and
does whatever?  Sure, it's nicer syntax, but we've repeatedly rejected
patches that only provided nicer syntax on the grounds that syntax is
not free, and therefore syntax alone is not a reason to change the
core grammar.  What makes this case different?

There's definitely something to be said for that, since it entirely
eliminates the problem of providing wildcards and control over which
function(s) to check --- the user could write a SELECT from pg_proc
that slices things however he wants.
The trigger case would presumably take arguments matching pg_trigger's
primary key, viz check_trigger(trig_rel regclass, trigger_name name).

Yes...

But as for needing "core support", we do need to extend the API for PL
validators, so it's not like this could be done as an external project.

Well, the plpgsql extension could install a function
pg_check_plpgsql_function() that only works on PL/pgsql functions, and
other procedural languages could do the same at their option. I think
we only need to extend the API if we want to provide a dispatch
function so that you can say "check this function, whatever language
it's written in" and have the right checker get called. But since
we've already talked about the possibility of having more than one
checker per language doing different kinds of checks, I'm not even
sure that "the checker" for a language is a concept that we want to
invent.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#7)
Re: poll: CHECK TRIGGER?

2012/3/5 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 5, 2012 at 11:41 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

I confess to some bafflement about why we need dedicated syntax for
this, or even any kind of core support at all.  What would be wrong
with defining a function that takes regprocedure as an argument and
does whatever?  Sure, it's nicer syntax, but we've repeatedly rejected
patches that only provided nicer syntax on the grounds that syntax is
not free, and therefore syntax alone is not a reason to change the
core grammar.  What makes this case different?

There's definitely something to be said for that, since it entirely
eliminates the problem of providing wildcards and control over which
function(s) to check --- the user could write a SELECT from pg_proc
that slices things however he wants.
The trigger case would presumably take arguments matching pg_trigger's
primary key, viz check_trigger(trig_rel regclass, trigger_name name).

Yes...

But as for needing "core support", we do need to extend the API for PL
validators, so it's not like this could be done as an external project.

Well, the plpgsql extension could install a function
pg_check_plpgsql_function() that only works on PL/pgsql functions, and
other procedural languages could do the same at their option.  I think
we only need to extend the API if we want to provide a dispatch
function so that you can say "check this function, whatever language
it's written in" and have the right checker get called.  But since
we've already talked about the possibility of having more than one
checker per language doing different kinds of checks, I'm not even
sure that "the checker" for a language is a concept that we want to
invent.

There is not multiple PL checker function - or I don't know about it.

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#4)
Re: poll: CHECK TRIGGER?

2012/3/5 Robert Haas <robertmhaas@gmail.com>:

On Sat, Mar 3, 2012 at 9:23 PM, Alvaro Herrera
<alvherre@commandprompt.com> wrote:

Uh!  Now that I read this I realize that what you're supposed to give to
CHECK TRIGGER is the trigger name, not the function name!  In that
light, using CHECK FUNCTION for this doesn't make a lot of sense.

Okay, CHECK TRIGGER it is.

I confess to some bafflement about why we need dedicated syntax for
this, or even any kind of core support at all.  What would be wrong
with defining a function that takes regprocedure as an argument and
does whatever?  Sure, it's nicer syntax, but we've repeatedly rejected
patches that only provided nicer syntax on the grounds that syntax is
not free, and therefore syntax alone is not a reason to change the
core grammar.  What makes this case different?

before argumentation for CHECK TRIGGER I show a proposed PL checker function:

FUNCTION checker_function(regprocedure, regclass, options text[],
fatal_errors boolen)
RETURNS TABLE (functionoid oid, lineno int, statement text, sqlstate
text, message text, detail text, hint text, position int, query)

this function is worker for CHECK FUNCTION and CHECK TRIGGER
statements. The possibility to call this function directly can enable
thousands combinations - all functions, all functions from schema, all
functions that has name starts with, ...

for user friendly there are interface: CHECK FUNCTION and CHECK TRIGGER

* provides more practical reports with caret positioning than SRF function
* support often used combinations of requests - all functions from one
language, all functions from schema, all functions by one user

CHECK FUNCTION is clear - and there are no disagreement

There are two possibilities for checking triggers

a) some like CHECK FUNCTION trgfunc() ON table_name

b) existing CHECK TRIGGER t1_f1 ON table_name;

these forms are almost equal, although CREATE TRIGGER can provide more
unique information for checking. And joining table_name to TRIGGER has
bigger sense then to FUNCTION (in one statement).

When I try to look on some multicheck form:

a) CHECK FUNCTION ALL ON table_name
b) CHECK TRIGGER ALL ON table_name

then more natural form is @b (for me). Personally, I can live with
one, both or second form, although I prefer CHECK TRIGGER.

notes?

Regards

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#9)
Re: poll: CHECK TRIGGER?

Hello

When I try to look on some multicheck form:

a) CHECK FUNCTION ALL ON table_name
b) CHECK TRIGGER ALL ON table_name

then more natural form is @b (for me). Personally, I can live with
one, both or second form, although I prefer CHECK TRIGGER.

I though some time more.

if somebody would to check all custom function, then he can write

CHECK FUNCTION ALL

what about triggers?

CHECK TRIGGER ALL

but if we don't implement CHECK TRIGGER, then this statement will look like

CHECK FUNCTION ALL ON ALL ???

and this is unclean - probably it doesn't mean - check trigger
function with any table. So this is other argument for CREATE TRIGGER.

Nice a day

Pavel

Show quoted text

notes?

Regards

Pavel

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#10)
Re: poll: CHECK TRIGGER?

Hello

Robert, please, can you comment to this issue? And other, please. I am
able to fix syntax to any form where we will have agreement.

Regards

Pavel

2012/3/6 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

Hello

When I try to look on some multicheck form:

a) CHECK FUNCTION ALL ON table_name
b) CHECK TRIGGER ALL ON table_name

then more natural form is @b (for me). Personally, I can live with
one, both or second form, although I prefer CHECK TRIGGER.

I though some time more.

if somebody would to check all custom function, then he can write

CHECK FUNCTION ALL

what about triggers?

CHECK TRIGGER ALL

but if we don't implement CHECK TRIGGER, then this statement will look like

CHECK FUNCTION ALL ON ALL ???

and this is unclean - probably it doesn't mean - check trigger
function with any table. So this is other argument for CREATE TRIGGER.

Nice a day

Pavel

notes?

Regards

Pavel

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#11)
Re: poll: CHECK TRIGGER?

On Wed, Mar 7, 2012 at 12:17 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Robert, please, can you comment to this issue? And other, please. I am
able to fix syntax to any form where we will have agreement.

Well, so far I don't see that anyone has offered a compelling reason
why this needs core syntax support. If we just define a function
called plpgsql_checker() or plpgsql_lint() or whatever, someone can
check one function like this:

SELECT plpgsql_checker('myfunc(int)'::regprocedure);

If they want to check all the functions in a schema, they can do this:

SELECT plpgsql_checker(oid) FROM pg_proc WHERE prolang = (SELECT oid
FROM pg_language WHERE lanname = 'plpgsql') AND pronamespace = (SELECT
oid FROM pg_namespace WHERE nspname = 'myschema');

If they want to check all functions they own, they can do this:

SELECT plpgsql_checker(oid) FROM pg_proc WHERE prolang = (SELECT oid
FROM pg_language WHERE lanname = 'plpgsql') AND pronamespace = (SELECT
oid FROM pg_authid WHERE rolname = 'myrole');

Any other combination of things that they want to check can be
accommodated by varying the WHERE clause. If we think there are
common cases like the above that we want to make easy, we can do that
by creating wrapper functions called, e.g.
plpgsql_check_namespace(text) and plpgsql_check_role(text) that are
just SQL functions that execute the query mentioned above. If we find
that the convenience functions we've added don't cover all the cases
we care about, it's a lot easier and less invasive to just add a few
more than it is to invent new syntax. Moreover, it doesn't require a
major release cycle: using functional notation, a customer who wants
to check all security definer functions owned by roles that are
members of the dev group can do it just by adjusting the queries shown
above. If they want an extra convenience function for that, they can
easily define one. Even with dedicated syntax it's still possible to
define convenience functions by wrapping the CHECK FUNCTION statement
up in a user-defined function that dynamically generates and executes
SQL and then calling it repeatedly from a query, but that's more work.

As things stand today, the "checker" infrastructure is a very large
percentage of this patch. Ripping all that out and just exposing this
as a function, or a couple of functions, will make the patch much
smaller and simpler, and allow us to focus on what I think we really
should be worrying about here, which is the quality and value of the
tests being performed. I don't necessarily say that it could *never*
make sense to add any syntax support here, but I do think there's no
real clear need for it and that, inasmuch as this is a new feature, it
doesn't really make sense for us to commit ourselves to more than
necessary. tsearch lived without core support for several releases
until it became clear that it was a sufficiently important feature to
merit tighter integration. Furthermore, the functional syntax being
proposed here is exactly the same kind of syntax that we use for many
other things that are arguably far more important. If
pg_start_backup() isn't important enough to be implemented as an SQL
command rather than a function, then I don't think this is either.

Just to be clear, I am not proposing that we get rid of CHECK TRIGGER
and keep CHECK FUNCTION. I'm proposing that we get rid of all of the
dedicated syntax support, and expose it all through one or more
SQL-callable functions. If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#12)
Re: poll: CHECK TRIGGER?

Robert Haas <robertmhaas@gmail.com> writes:

Just to be clear, I am not proposing that we get rid of CHECK TRIGGER
and keep CHECK FUNCTION. I'm proposing that we get rid of all of the
dedicated syntax support, and expose it all through one or more
SQL-callable functions.

This seems entirely reasonable to me. The syntax support is not the
value-add in this patch, and it's been clear since day one that it would
be difficult for the syntax to cover all the likely permutations of
"which functions do you want to check?". A function-based interface
seems like both less work and more functionality. Yes, it's marginally
less convenient for simple cases, but I'm not even sure that we know
which "simple cases" are going to be popular. We can and should
postpone that decision until we have some field experience to base it on.

If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

FWIW, I would suggest check_trigger(regclass, name) not tgoid, because
we do not have a regtrigger convenience type (and I don't think it's
worth adding one).

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker. Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

regards, tom lane

#14Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#12)
Re: poll: CHECK TRIGGER?

2012/3/7 Robert Haas <robertmhaas@gmail.com>:

On Wed, Mar 7, 2012 at 12:17 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Robert, please, can you comment to this issue? And other, please. I am
able to fix syntax to any form where we will have agreement.

Well, so far I don't see that anyone has offered a compelling reason
why this needs core syntax support.  If we just define a function
called plpgsql_checker() or plpgsql_lint() or whatever, someone can
check one function like this:

SELECT plpgsql_checker('myfunc(int)'::regprocedure);

If they want to check all the functions in a schema, they can do this:

SELECT plpgsql_checker(oid) FROM pg_proc WHERE prolang = (SELECT oid
FROM pg_language WHERE lanname = 'plpgsql') AND pronamespace = (SELECT
oid FROM pg_namespace WHERE nspname =  'myschema');

If they want to check all functions they own, they can do this:

SELECT plpgsql_checker(oid) FROM pg_proc WHERE prolang = (SELECT oid
FROM pg_language WHERE lanname = 'plpgsql') AND pronamespace = (SELECT
oid FROM pg_authid WHERE rolname =  'myrole');

Any other combination of things that they want to check can be
accommodated by varying the WHERE clause.  If we think there are
common cases like the above that we want to make easy, we can do that
by creating wrapper functions called, e.g.
plpgsql_check_namespace(text) and plpgsql_check_role(text) that are
just SQL functions that execute the query mentioned above.  If we find
that the convenience functions we've added don't cover all the cases
we care about, it's a lot easier and less invasive to just add a few
more than it is to invent new syntax.  Moreover, it doesn't require a
major release cycle: using functional notation, a customer who wants
to check all security definer functions owned by roles that are
members of the dev group can do it just by adjusting the queries shown
above.  If they want an extra convenience function for that, they can
easily define one.  Even with dedicated syntax it's still possible to
define convenience functions by wrapping the CHECK FUNCTION statement
up in a user-defined function that dynamically generates and executes
SQL and then calling it repeatedly from a query, but that's more work.

As things stand today, the "checker" infrastructure is a very large
percentage of this patch.  Ripping all that out and just exposing this
as a function, or a couple of functions, will make the patch much
smaller and simpler, and allow us to focus on what I think we really
should be worrying about here, which is the quality and value of the
tests being performed.  I don't necessarily say that it could *never*
make sense to add any syntax support here, but I do think there's no
real clear need for it and that, inasmuch as this is a new feature, it
doesn't really make sense for us to commit ourselves to more than
necessary.  tsearch lived without core support for several releases
until it became clear that it was a sufficiently important feature to
merit tighter integration.  Furthermore, the functional syntax being
proposed here is exactly the same kind of syntax that we use for many
other things that are arguably far more important.  If
pg_start_backup() isn't important enough to be implemented as an SQL
command rather than a function, then I don't think this is either.

Just to be clear, I am not proposing that we get rid of CHECK TRIGGER
and keep CHECK FUNCTION.  I'm proposing that we get rid of all of the
dedicated syntax support, and expose it all through one or more
SQL-callable functions.  If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

this is very near to my original design. This design is general so we
really don't special statements - because any action can be processed
in combination query to pg_catalog and calling checker function.

The most expected value of special statements is simplicity for usage
- checking function is common task - it should be called significantly
more often than pg_start_backup() or some tsearch maintaining
procedures.

just: "check function name()" is more shorter than "select
plpgsql_check_function('name()')" and what is important - it is
supported by autocomplete in psql. Other arguments is possibility to
show result.

A special statement has higher possibility to put output more clean
and readable - I am not able do it in function.

I agree, so this patch is relative long, but almost all code in these
statement is related to multiple checking. When I remove it (or when I
simplify) - I can significantly reduce patch (or this part)

But still I think so some reduced CHECK statements is good idea

mainly for:

* autocomplete support
* more readable output in terminal

I don't know if this is enough

Regards

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#15Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#13)
Re: poll: CHECK TRIGGER?

On Wed, Mar 7, 2012 at 12:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

FWIW, I would suggest check_trigger(regclass, name) not tgoid, because
we do not have a regtrigger convenience type (and I don't think it's
worth adding one).

I'm OK with either one.

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql. It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#16Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#13)
Re: poll: CHECK TRIGGER?

2012/3/7 Tom Lane <tgl@sss.pgh.pa.us>:

Robert Haas <robertmhaas@gmail.com> writes:

Just to be clear, I am not proposing that we get rid of CHECK TRIGGER
and keep CHECK FUNCTION.  I'm proposing that we get rid of all of the
dedicated syntax support, and expose it all through one or more
SQL-callable functions.

This seems entirely reasonable to me.  The syntax support is not the
value-add in this patch, and it's been clear since day one that it would
be difficult for the syntax to cover all the likely permutations of
"which functions do you want to check?".  A function-based interface
seems like both less work and more functionality.  Yes, it's marginally
less convenient for simple cases, but I'm not even sure that we know
which "simple cases" are going to be popular.  We can and should
postpone that decision until we have some field experience to base it on.

If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

FWIW, I would suggest check_trigger(regclass, name) not tgoid, because
we do not have a regtrigger convenience type (and I don't think it's
worth adding one).

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

I am able to drop all coded functionality and preparing these simple
check function is relative simple work. But I am not sure if we can
design better interface in future. It is relative well designed and
consistent with other statements.

I know so you and Robert are busy, but if you can have a 10 minutes,
try it play with it. There are nice autocomplete and "well" output. It
working well in psql.

Regards

Pavel

Show quoted text

                       regards, tom lane

#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#15)
Re: poll: CHECK TRIGGER?

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 7, 2012 at 12:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker. �Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql. It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

That argument is just silly. The only reason there's only one checker
function is that that's all Pavel has bothered to write yet, and all
that he's likely to write since (AFAICT) he doesn't care about the other
PLs. But other people do. There is certainly value in being able to do
checking of other languages, and if we don't set this up properly now,
we're going to have problems with having to change the user-visible API
later.

I said from the beginning that I thought the most important part of this
patch was getting the API for the language-specific validator functions
right, and I remain of that opinion. If we're going to blow that off
then we should forget the patch entirely until we have time to do it
right.

regards, tom lane

#18Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#17)
Re: poll: CHECK TRIGGER?

2012/3/7 Tom Lane <tgl@sss.pgh.pa.us>:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 7, 2012 at 12:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql.  It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

That argument is just silly.  The only reason there's only one checker
function is that that's all Pavel has bothered to write yet, and all
that he's likely to write since (AFAICT) he doesn't care about the other
PLs.  But other people do.  There is certainly value in being able to do
checking of other languages, and if we don't set this up properly now,
we're going to have problems with having to change the user-visible API
later.

I said from the beginning that I thought the most important part of this
patch was getting the API for the language-specific validator functions
right, and I remain of that opinion.  If we're going to blow that off
then we should forget the patch entirely until we have time to do it
right.

I believe so with some minimal support for other languages - tj
check_function, there will be other checker functions early. Preparing
plpgsql_check_function instead check_function save 10 lines of code,
and we will close door to other.

I am working on some minimalistic patch

Pavel

Show quoted text

                       regards, tom lane

#19Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#17)
Re: poll: CHECK TRIGGER?

On Wed, Mar 7, 2012 at 2:03 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 7, 2012 at 12:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql.  It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

That argument is just silly.  The only reason there's only one checker
function is that that's all Pavel has bothered to write yet, and all
that he's likely to write since (AFAICT) he doesn't care about the other
PLs.  But other people do.  There is certainly value in being able to do
checking of other languages, and if we don't set this up properly now,
we're going to have problems with having to change the user-visible API
later.

If we publish plpgsql_check(regproc) now and a year from now we
publish anypl_check(regproc), the former will still work. There's no
need for an API break there.

I said from the beginning that I thought the most important part of this
patch was getting the API for the language-specific validator functions
right, and I remain of that opinion.  If we're going to blow that off
then we should forget the patch entirely until we have time to do it
right.

Well, I guess I'm still of the opinion that the real question is
whether the particular lint checks that Pavel's implemented are good
and useful things. Has anyone spent any time looking at *that*? I'm
not going to stand here and hold my breath over the interface, but it
seems to me that if we don't know that we've got a worthwhile set of
underlying functionality, sweating the interface too much is putting
the cart before the horse.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#20Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#19)
Re: poll: CHECK TRIGGER?

2012/3/7 Robert Haas <robertmhaas@gmail.com>:

On Wed, Mar 7, 2012 at 2:03 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Wed, Mar 7, 2012 at 12:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql.  It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

That argument is just silly.  The only reason there's only one checker
function is that that's all Pavel has bothered to write yet, and all
that he's likely to write since (AFAICT) he doesn't care about the other
PLs.  But other people do.  There is certainly value in being able to do
checking of other languages, and if we don't set this up properly now,
we're going to have problems with having to change the user-visible API
later.

If we publish plpgsql_check(regproc) now and a year from now we
publish anypl_check(regproc), the former will still work.  There's no
need for an API break there.

I said from the beginning that I thought the most important part of this
patch was getting the API for the language-specific validator functions
right, and I remain of that opinion.  If we're going to blow that off
then we should forget the patch entirely until we have time to do it
right.

Well, I guess I'm still of the opinion that the real question is
whether the particular lint checks that Pavel's implemented are good
and useful things.  Has anyone spent any time looking at *that*?  I'm
not going to stand here and hold my breath over the interface, but it
seems to me that if we don't know that we've got a worthwhile set of
underlying functionality, sweating the interface too much is putting
the cart before the horse.

the core is based over plpgsql_lint - and this is used in some
companies about year

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#21Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#19)
Re: poll: CHECK TRIGGER?

Robert Haas <robertmhaas@gmail.com> writes:

Well, I guess I'm still of the opinion that the real question is
whether the particular lint checks that Pavel's implemented are good
and useful things. Has anyone spent any time looking at *that*? I'm
not going to stand here and hold my breath over the interface, but it
seems to me that if we don't know that we've got a worthwhile set of
underlying functionality, sweating the interface too much is putting
the cart before the horse.

No, that's backwards. I have every expectation that the specific set
of checks will be extended and improved in future. But changing the
framework, if we don't bother to get that right to start with, will be
much less pleasant.

Not that having useful checks is not an important part of the patch, of
course. But it's not what everyone has been focusing on, and I do not
believe that we were mistaken to be more worried about the framework.

regards, tom lane

#22Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#13)
1 attachment(s)
Re: poll: CHECK TRIGGER?

Hello

2012/3/7 Tom Lane <tgl@sss.pgh.pa.us>:

Robert Haas <robertmhaas@gmail.com> writes:

Just to be clear, I am not proposing that we get rid of CHECK TRIGGER
and keep CHECK FUNCTION.  I'm proposing that we get rid of all of the
dedicated syntax support, and expose it all through one or more
SQL-callable functions.

This seems entirely reasonable to me.  The syntax support is not the
value-add in this patch, and it's been clear since day one that it would
be difficult for the syntax to cover all the likely permutations of
"which functions do you want to check?".  A function-based interface
seems like both less work and more functionality.  Yes, it's marginally
less convenient for simple cases, but I'm not even sure that we know
which "simple cases" are going to be popular.  We can and should
postpone that decision until we have some field experience to base it on.

If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

FWIW, I would suggest check_trigger(regclass, name) not tgoid, because
we do not have a regtrigger convenience type (and I don't think it's
worth adding one).

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

here is implementation of reduced patch - it is not final - no doc, no
regress test, just only functional interface

postgres=> \sf fx
CREATE OR REPLACE FUNCTION public.fx()
RETURNS SETOF text
LANGUAGE plpgsql
AS $function$
begin
return next 'In function f1():';
return next 'error:afasdf:asfsafdsgfsgf' || a;
return;
end;
$function$
postgres=> select pg_check_function('fx()');
pg_check_function
-----------------------------------------------------
In function fx():
error:42703:4:RETURN NEXT:column "a" does not exist
Query: SELECT 'error:afasdf:asfsafdsgfsgf' || a
-- ^
(4 rows)

caret is on correct position

I'll prepare check_trigger function tomorrow

Regards

Pavel

Show quoted text

                       regards, tom lane

Attachments:

reduced_pl_checker_2012-03-07.patch.gzapplication/x-gzip; name=reduced_pl_checker_2012-03-07.patch.gzDownload
#23Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#13)
1 attachment(s)
Re: poll: CHECK TRIGGER?

Hello

2012/3/7 Tom Lane <tgl@sss.pgh.pa.us>:

Robert Haas <robertmhaas@gmail.com> writes:

Just to be clear, I am not proposing that we get rid of CHECK TRIGGER
and keep CHECK FUNCTION.  I'm proposing that we get rid of all of the
dedicated syntax support, and expose it all through one or more
SQL-callable functions.

This seems entirely reasonable to me.  The syntax support is not the
value-add in this patch, and it's been clear since day one that it would
be difficult for the syntax to cover all the likely permutations of
"which functions do you want to check?".  A function-based interface
seems like both less work and more functionality.  Yes, it's marginally
less convenient for simple cases, but I'm not even sure that we know
which "simple cases" are going to be popular.  We can and should
postpone that decision until we have some field experience to base it on.

If we need both
plpgsql_check_function(procoid) and plpgsql_check_trigger(tgoid), no
problem.

FWIW, I would suggest check_trigger(regclass, name) not tgoid, because
we do not have a regtrigger convenience type (and I don't think it's
worth adding one).

More importantly, I do not agree with requiring the user to specify the
language name --- that is, it should be check_function(procoid) and have
that look up a language-specific checker.  Otherwise, scenarios like
"check all my functions regardless of language" are too painful.
There is value-added in providing that much infrastructure.

here is updated patch (with regress tests, with documentation).

I removed a CHECK FUNCTION and CHECK TRIGGER statements and replaced
it by pg_check_function and pg_check_trigger like Tom proposed.

The core of this patch is same - plpgsql checker, only user interface
was reduced.

postgres=> select pg_check_function('f2()');
pg_check_function
---------------------------------------------------------------
error:42703:3:RETURN:column "missing_variable" does not exist
Query: SELECT 'Hello' || missing_variable
-- ^
(3 rows)

postgres=> select pg_check_trigger('t','t1');
pg_check_trigger
--------------------------------------------------------
error:42703:3:assignment:record "new" has no field "b"
Context: SQL statement "SELECT new.b + 1"
(2 rows)

Regards

Pavel

Show quoted text

                       regards, tom lane

Attachments:

reduced_pl_checker_2012-03-08_1.patch.gzapplication/x-gzip; name=reduced_pl_checker_2012-03-08_1.patch.gzDownload
#24Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Robert Haas (#19)
Re: poll: CHECK TRIGGER?

Robert Haas wrote:

Well, I guess I'm still of the opinion that the real question is
whether the particular lint checks that Pavel's implemented are good
and useful things. Has anyone spent any time looking at *that*?

Actually, I did when I reviewed the patch the first time round.
I think that the checks implemented are clearly good and useful,
since any problem reported will lead to an error at runtime if
a certain code path in the function is taken. And if the code path
is never taken, that's valuable information too.

I don't say that there are no good checks missing, but the ones
that are there are good IMO.

Yours,
Laurenz Albe

#25Petr Jelinek
pjmodos@pjmodos.net
In reply to: Pavel Stehule (#23)
1 attachment(s)
Re: poll: CHECK TRIGGER?

On 03/08/2012 08:35 AM, Pavel Stehule wrote:

Here is updated patch (with regress tests, with documentation).

I removed a CHECK FUNCTION and CHECK TRIGGER statements and replaced
it by pg_check_function and pg_check_trigger like Tom proposed.

The core of this patch is same - plpgsql checker, only user interface
was reduced.

postgres=> select pg_check_function('f2()');
pg_check_function
---------------------------------------------------------------
error:42703:3:RETURN:column "missing_variable" does not exist
Query: SELECT 'Hello' || missing_variable
-- ^
(3 rows)

postgres=> select pg_check_trigger('t','t1');
pg_check_trigger
--------------------------------------------------------
error:42703:3:assignment:record "new" has no field "b"
Context: SQL statement "SELECT new.b + 1"
(2 rows)

I did rereview and rechecked with few dozen of real-world functions, and
it still looks good from my point of view. I made bit of adjustment of
english in new comments and Pavel sent me privately fix for proper
handling of languages that don't have checker function. Updated patch
attached.

Regards
Petr Jelinek

Attachments:

reduced_pl_checker_2012-03-08_3.patch.gzapplication/gzip; name=reduced_pl_checker_2012-03-08_3.patch.gzDownload
�B�XOreduced_pl_checker_2012-03-08_3.patch�\{SI��[|�]�i�0~�bY!{�������v��KR/�r?�������������M��F1cZ]YYYYY���*���9�t~�����,��a�����'6����G��������t�������>����N����sg;�?��u�/_�<j�a����+ooG����Y���r'��~��]�7rB��ju�������~��a���]��<��l��%gn��<�Y�����Y���1�;2x��$�]?V����;�	n���x����@?|�z~��Q��<cE�H��� Xu[����8�3������;lg>��{gM%K���8�^'Ic�{E�������-���Ns��P�zSd�=A��/����$����Y�Gw0��
���Y���-������	����H��q��`E�S���q����<���b���+�
���xO��O�O�F��9�:d�4.��Av�<R����;���N��ap
K��W`p�=����	��4b�U��t�37�[5��������C�boUZ}PL�gvJ[1�ih���N��B�/�[�T,��MLN�8��T�]�f�I�)_�U)��N{��iO1-g��MIN�4er4���I"V��JU��s)SZ���R2���
�,3��6$��ct���,��z%���$��GW8��h;J���������=��4&��F��u
Lr�	���K��`B�MpIy��������'Z��w��?�����\T�X�SL!#����Kow���ba�g9�,�kg��������f6��n{7����m6�z�M4;�����1y������WGo������(�%�r7N=���C�-��E1�����&t��[v��K�Q�c7����ugl����:b`4Y�Q�h��;�[-������z�p��	|0�����&��!�c�bJ��K�m�&�����[�xE�f���E��I}
���9�U��0D�������
����� �e����Z�~��:<V����f���&:R�}DJL"�~
H����m��I��t#���J���x��5�w���;y�=pk�7�����o��CD���q�p2:�������O�F,	2�(sW>�z���X,�����8�k�q�z�b���)8*7����,�#\�-���N��X]T
����?�-_v���l`���l_��L97�����	?:w�`�b�B���"��j���� ����p\���f9�e���Hp��%�Q']b?���J�0�9��H~C^�����Z�N�p��
��bo�U��}:b������D�O�`�J#��I!������5<]����o�����	O1�Bu<��K��a����.��w��jq�{2#D�Imm�G���mV$I��2���h��r���Ld��w�R�M�bH��8a�n%�D�sxp�>a��/�Go�7F���!m���k������A��lv��C�S��9�[P�48�������E
��(dm�f��	�'x4���F`���	fA	��,�������Y�0Qn'k�����Rp��8���;�)��!
�J���������D��A�#����m�����2in�q�9�M������' (�I�a����KF�YSw9�Q���"�%���z�Q.���B�eh�[�NQD�T��;~����
���F+�h�o���-T�d���o�F�$�@�'+jUs�/�Z?���K�����y��V�����Z�hU�
��y�}��]|��Z�q������ ��a�#�ijn_?�6���mt*�����
Xk��������zA����cx��5k�mxJ�S_L��+����:�u_��]M������Bl�X)�^��x���L�q��*���`Q����/_��PY��h�lW��#�2���)�w�{/)J�F�����C�8����C_��� �>���2�_���
���M����� >2=�u��@&�s5�*���T�#7( BG�"���!/�A��8����f��J��&n^ �I
�?�WW��n#�j�RyN&0�b���KA17�:��UR	��;P�`AQh����R����E0�p
c�m�ZB��r�Xg����J�*�F��!|
�<5��x�9�;s���)=��R���?�����h��/"�bvbT��=���e�l�u�fS��N��o������������?�ne�!+�JKp���
��oD��8� dD�;�J�8�k+�����MF7����
>�L����O��K����)���q9�.rjgM�N��� 4�0�|���.�u0��~9���M�������
4��_}Uar[���~=�_�O���`�U��j'�9���������Y�@nn���hS�$�|�\���{�)����(�_�/�|���kGV�t*#?Q�	��|s����T�Z���[��0��}RMYM��oW�iO��j�RHU:�I|����$�8�h@�p�C�	��N95���O3�8�������������iO���_��N�>�&%����*q�a@&�F�����qj�"��������#�s_P�i	J_�=�)f�O����+R�\U��
�=i��+Q����[�wsv���L?�WP��D�[�U�3mL�7��H"�%�cUl����IC(�
.�_�3��j'#�.$c% �qR8!O1m��<W�I��i7{���={��&�����Sp�\Y8{�D�=w.��+g�d�uP���9������W�Y%	�Y'��h�D��4���z�d��fE�8�-sL�T�i-�1�w��wdR�|=�("c0���"fx��e�$|�*)���]����
�H�4p�,����h��EI�k��&1M���i�~rs\X��s���u�,�U�<)�����M��L0�dH�~�����g���
���B��*������s��r������&GO�#R��P"n<~����?����C��.�`/z���}g����Y!Pl��Rnk��q��oh�1��,*�q������p���3���]:iM���\����#�!���@�T���9����h	�6����������p@T��"���������`��E
��.�h\���A��I����Q��<���]Sl|or\k�?N�'�'����?��3�s�(�<�}�^���C�S[(�	^�@�"��/�I�9�"i�����&��c��o���i�sL�e��
z0�U2C��'�@`�U�����
��!���m���#q�C+��@B�(��fJ��#@}R��<���������pf���}Jo��^��  �AW*n�b>R��%����J��'����y�>�����D8g\�m�����2������C0b_�^����n��o��]�����x~��Eh�����p�~��
s�g{5E[��6�����y{4�N
=��F���&�(�����������U�{;�$��������R�}>�p��,��/n������)���iX��
qY����h��4����j
-6w��3��3�/�G"aB�i��a�#����+���q�eN1��<4U�+2kE�1���B��Rs=cW�./�
�O��\�n��Z-��6��5�L�'r� �D�����������dd_]O��O77�����%�1����jB���w��L��Q
�?9[}�2Q�l�T�7[-Z=��OR�������C{�n�O>�A���>����B<�N����Z�y2�n��+���B�tO��T����F\��
�j�v�I�i� Z�����*}pI�O_r�$��P��eA����B��.�������������z�d���I�rh���~���_���wo������C����r�����d�����R��@��
^l6J�������TK��*cRxw�`�Yj�o�HC�P��&
J�[�I�b"����h���d���f1�\l���u���W�:K��e-�����0[|?,mf�[]>��Fc��}gl
�! osZ���t���}c�[���B�C�������G�x,x�X���|AD8���8O�9������F$|���P,��4K�u_�{N�2Z�Q����N�!��0��BS6*r�*��Z��X�[�t��g&6���Z����0	L���}��J�������ei����<��z�A�fY�o��}��@umH(�����?Q�^�����2:F:�^E���1��5�d�X��r:N�����^#,�� ���rX��A>o��:�A�/�m?(n��
t�X�^����O9O����8E%K�<�&�Y��&Vs#�'v��{��d#��
J�����h���T�tT������+m��x�����p9�V��.���!�F�&�kAI`�����x�h<��}g�'x4�����s��0�x�b��rM_�X�[�&N�����yI��1��|5�
%+<��KU��Z5���4���Yj�	�+� �d��������_#�����2��<AS�Yi'
��)ci�)�	��@�3�/#?�5Gl�rdJ�-�6jS2�!�K�JYc�b���h�:��s<��/7��9�4)���es���6D�|e���%��=G�;����l�E���c�5���=�?��y�V�8�'ei��0!���/��l�l������x�#����!��;���\�T��	����uC1��FK,��&Jt�`u�f\���C�\|�]!&t1$/wQ�}=q�N���;?)/_��+:�4��� ������tpK������b���N'��������=Uf��+���c[U��D�H�<�|�R^yQ��v�J!�Gj�Y:]��'������\G����
Ta5�������Jd�<�b
���&x*��D��F��!`Zx��o�ebE����Z�
��b���K;�+���v���@��i����9�T'��e�%I�;�Zf#��n�@�I^���. ��6�/�#�A�*QC��4�~v�_��y8%�w���Wx]N�
���~��v�Stw�������L���$�&�	��&�i	����FPlV2�E���u�s�k�b0R���u���~�a���q#���5���6@	uG�F{���@P������W��Zc�������������z!�Hk/�^]��~ZO�[�����<��v���:^����WS
6a`��T�)��'aO�uRH��%�� ��C���s�o�"z���Z��.�������kk�2�:��m����~@v���?8S�g�^@PM��5���_^^oGS�b�������8�����kd����)t���db�b�[���
K���2@*�c���]�\mV�Y!��3��U!;	#j� ����;;j/1�����%6�9L����W�hT��QT��[�M3�������U�;�s�\�x�0�no��JPWmV5�6{��M#�7[h�������&�4�q�!�����?go���bO��[��������������	��ZId�V���S:������o��OI�(-������_g]
o+2�`���3.��V�p��0������s�X�o�e7����*+�������.���}��d(Ov�=����VE7���]i&��\�K�)I��hm`�$"�{�?�#��n���s6���h2Rp��9dC�
�����;�rt�P��w�l�AGR���� ��x��P��*�Q�8c�Ll�C�-;�u��*3 ?ZS�:��2�o�����+Io���k�K��{������R���{zAw�~�+G�+��'����0g!���w����v(bo�/�_r>;#5������;�VL�X+�������?ABID�����f�������w�2�<��0���T���[�*�-�@���& |����_�y/kV��U��4
�����\�L��\���G':�-����`�����;��*������:���2�"F����}y_W����S�a� @������� ���@@�������������d�$������&	����������S��N-Y���R�7���^b�a5�ln�I�,�zX��f��T0�����%��2����"o=rG�����9��7������E�w�sl����[k/�'9�w��ZV�:��&~JU��s}5���NM�|8���5���T�A9����0�����5Uq���Hh4!���Y���s�x`�r�JE� �g
�J>*\���U�4���p}N���������R�I����b��T��W^�4jd�C|a�f��y4O7�P���i4����c!���?�5���*������*%��d��=X����FEt�����<B?	���m,�jYU��\�����XL��f�x�/���Z�F
"K��g����+���=
��O*B��c�t���{��K�3n]_�+?�3���j�B-9��g1p�:8�-��I�P#S*q<U�;���:�7���t��J��E7_�Q���������W��:�g����K�}}&�]���B�df�xM�z1���;J��w���H����l���<73p����l��#n�py��5Q)�����>f�pf�1D3PgTtM�n��K�hL��dR����+��p��>e��u��t�����[��Z�����v�8�Ym8�Y���n�Q�G�p�~��_&�C���V�,���R�PN8��Z�����j:�]�q�}4 7��������MB5��M�
��v�C�h��3�'T�O��~|GAp�+����:W�o��8���/��/�ty�J�
������Rj�dF���������`���������.,q|��7����^����cf)60l��3�T>�����q�;��e\�\g;�x�������f��gg?y%������,_�o�����x05k�\P�;�$	w������T�*�������7��->��^�I�`�����Pk-P�z��SM5l���D<�oo�w��J�
<a��h-��o����<�|�

�/($�o����?��4)����[����=�P]c��;�l*	��6(���u������(�uxY6pV@i�6E���{���U�]i���kZ�'d��b'��77�E��-z�����.�W������%Z�f���8	=5E�f�[��:K��}���#��u���<�����(b
/�����o-�'h�J���.�EtH���=u��MR^��`��\���Dg1*
��/A]��w��T�t�/v��������������N�Bg"������wvQY��=��^^~hW������
��5�}����Wi��_������T"G�hA�;��	x����6&���ZE'�X6��FY�������B
$�����"�%�;�����=�]f���wZj�|�q`���s�4/�f�T�nle�ZU�j��pL$�8R
�����j��K"�D}�O]W�k����%4=�	s'������u��t�4����%7���t��
.����Z1)��4]�9�\���[���}|~���8~���:>i�%d�t������
����Z|L�E�['���u�5��7���o��O�s�/4�d!����.��oo��_|qt�Gn~�8��&�����7���l�w�?�����P.�{��'e�]����[���N����[9��]�`{��:v7V���M\j'�W
���j����/���k���-RO��Rf�k��� s6���$�F?W�cU8eY�����?����_J�0�1J3�u-�8�^R���J�K�qB��J�r^~-��������=��{�F�S�J���I��z�ln�����?+3^�k'�VA�1w����1�����~k�0�L�*DU
/������������E������K#�����ij�U���_a(J]��\��)9���)��vS��	�'���~<�9�g��GB5>U�mpm�C�[��������v�1�)7�fA;�0��b	�8"��n��T#l���q����'A�nQ}N\�6o#�ncs�Q��w���%N�u��h0�>\����`���oLd��8m]�.N['?z�������\����n���eP����h����D�M�5
�q�p�
����.��A��R�e��u���|�X���$��E�U���B�?�g�^g�����U��.���^����]]��=��e���@T�VC�=K9�^�pf�R�#�,����IW��q ��	�������S^���C�Y���;�N�C���x���V�e�
���aA-����7V�Y<\:G���+���M�w���
W�{������4z~��9����O���{'S���v�1}��a�F���f���s�9����l��?���)�ZW���q��w�<�d�WF)w=iB��+O$BdX��
1�"���E?[{L����������V�?������?�[�@>�������<�	 C�����c�m_���tr��/�{"H�i*B��������oT�a��.j�������FM5)VVm��iY.�M�����
�9����%����
b�'�����n�ag6
��^�}�na(H���o�;�C&��M4��m��2ax�����t���iN!'�gn*�	�F�����.�^D���$d��`}�r��][NQicY{=�1K{�"]��D,r���@�U!��mH �v�KmC����������/`JY9V�K��M#G��yF���
C�5����u�E�Dq��'���e�g�������,k	����d���F�W��lu����U�v����I���t!��[U�%�����Nc���B� '���3�eS1V7������������P�a%���W���?1�|�g6���)���B	s�yn�
zd8�����,Q��7����e�z,J����"���=�`�o&�����h�)?��D�C��W_�����#:�ICR*�����x`�J��?�dF�->Og�8�g=�tCK@�6iD��� ���u]P�P�B�NI�H1='�!)S3�R*���
���fc���&}2����P���F��U����r�*�g�G��M��5�C�8�9�'m��Q�����&���&p����j���r=hN�;�c1\�i�08to6�O��e��4�����=����A����g������|�����"�M��������$.O�w�G4�fS&-���(��W)t�G�i+�>4�?x����2�)
�s�I��-D�G0��I������Sr-}��8��
�g���e��*�g�p��$�T�,�����h|Lbry)��o]���e\�W���M�(���]��Y����W��~���A�d���N�M#e�V�������ZI��/�<�*���,kx����X���5i!�������zs}�������w������ysy}
���8C�N�	N�S�P�>�x������'��1���k����lo���Yzs� 9
�� KQ;�^�~����*J��s�����E�#�N�����5����2����4=��+�(���1�d�W�	�E�f}G~-
�������T�E���I[ @`z�q��[�BC	��69=J�bo3hh#���^'_��ZNcZ��R������*k�";��J�R%�1�9�m|�\@���\�Xc��CMh�3".'����� ��m�v��N�?�sxK#Y��B�!���~��0��xD�G�Q1c�������r��&������n9eV���b���#�L$S�(=��!�/�a�ml�N��!b:E>%�*����������y[�X�5�4���9�.�P�E��~����w�]�������u��Yivs�Q�)�b�
WV:�+���+x�x4��
�7�y��~��[`�i0�������`�P�
xl�IJ��p(��T&81^r�b|cV��2���U��s[���?@n�=I�0���#������?� �������]��u���0v�v����������)��r�	J��	��AF�6Q_C���+���*+������c�`)$B��������L�
�K�o<�)��k?��9�BB�]��u'�������k���Nw�w��w7G��c��/x�c/��!H�U��-�S��~lD5����SKL��G)���XPsE"�8�t\�Pp�N�ea��(�2
�+9����b���t��`����n���{�E)n�,Nq9Z$��
�������V9�������������[��\��qV��|I���:-�#'�����N+�198:�6���]5K,��E���w�������	��g�A0���yQ�9�
J�G#4�j�7|�;��J`����� �eS6�W a_
e���v7*���q�H��Q��R7�����q��1_���Xp����/V�ek@q�O���:�������0�>���5b��B��J�Fq)��K�&���+�-����4���ZQG�K;s�D^*��8\�3����pA^`�Ax=�(�w�I�2��D��B�h����p1�+�S�/�L�l�x)�
Q���KWo?��>���5����[eO�����&�i������6��jh�AL��O��z��@����R]��{a��a�`�p��� �{ ���L��{3s������T
`���+g�������g��v���@�EPc�Pt����!���wz;�Z����4:D�v
�Q�d�AjJV�����x��3����u���n�Y��=������C/�o� ����<?��s�:g���xOX��#�`M���5�t�5�.V��]?u���v�L���:�Y�:O�z��q�,>�M���bS��w'�d��	� yM�q*'
J�`���(���l�'����J���E�	�q'J�Cv7}�f�D����D��#9�*��WT�>�U��>��"M��^=�\�$�.v��1�o�����~zH��5��y���Z/�J_�~0����i��Iw?�Z���D�������zs[�A�xl�_���8��0�_ ��Y��9�f��lM�~f�=��|����w��W������y��M��ec�A4��h�
��?��3��H3o ���4�\����=,T������
y�V����Zjo����,����s��t��-�=������'����uycy�����<?��K��}Y3v���:o��_�L��������6�����W��^g{��V���ns1��[(���Y{�;��'�BRLE�B����/�b�Q�a�����B�i�������\�n��������+�RsDN��H���
��h�(��/V������5�#�����T������=�Q�P�l�����I>[7l�)��E�S��������q��J
0��	�LELk�`s��V.86%8�wQ�_��~�4��4�5�2�h�Y,��NG��C��,H*@-L<���|��\@Y�����N�{�m\�x���.hK�O�����#,��,����?��j����r�qg��(�^R��n���Zm�`�`��+�F�2�Q�R�=�E���V���O�D�Tj�u�H���Z�\������
��u���Fh#Z��YjJbW�p����n<&3CUSr����[����@6HN
���v�9����g�H"��$ZI�����4ppJB��,T>G�N������e?N�:������3������m��(�te���j61���N#���M �)D�[{�m��{��������o�������i����E��'e�6�t�%I�l�D�����0��,��������;;;�M�K�Moo��d4�^��B�2��m����;��I��(!s�[�&���p=j2�<�F�&l�p�.B�q�b-���(qd�nF+��VRW���p�C����c��.���<��o�4J?�R��)-:Eo%������}t��i����i���o�%��
�
n�������/q{^����x���7��u��
3k�7�h�_�Ui�e�J��Q��/��f����zm�_���N������_2U�J��z�S�>�� �,�^j��lp���aa����j�F/���gP��r���5��������uu>������v����7�(�%aQ��������c�
a�j����r�;}������s4�#��^����zT��2k�����I!�uI|�R*�K	6����$`�B����r�2��������
��@'p�S��	1��?��h~�B�O9z�?��n���U�o@�pl��#��|��@�5Y�yY%!bA�m�y���G�7K2'��Z��1`��>��	������cF���IN�'!�a��8�k�vc����1	�h�1����aM�wa�S�d���v�W84
��Jm?���*��$GJ?�&��
��o'��,("5yV�����jz}O�X�<�>C)�g�	N9�B'�����6f4M�2N�*]~�#��'�JZE�w8���o��\��AeW����[��tE1V���AM�w?u��?�"�c%��bjkzv�����c�T��+���9����>'�M�_��)e6-���c�!�~I���1Ci�1����A������Zs'��i�,r����Y�0�N�b���O��b,q�����D��	��zlQM����V9��� j��F�CK����:XB�����V�Sf��j���b��-����ex6����Io*�Ir���U _������QV��P��1A�w�$q��n'�P�<[��j��N��v��DX��M���[���5����y�CEK,0��;S^T�1���
���@�x
���8��5fz��p����A�����!����pEq��(�^Q����
0�R��&B����8
�y/��/����f�;D��#	���1wE�K7��
ex$oe�*e'Y

c"����=��A�!������q�l��#�C.��R�At+0v������)P�qN�.�4���L+F�p�D���-�Y{MA���l����0�
`q�;�;�y�&��n�0r�D�0m���������^JwB�$?��
��������XlUBMwrCy�R�7��:9�����^������{�,_ ��@38?������<����#� �<�P�V�*��S��<&j%��X7�[FK�s�Rp�3~b��eg�fT_�b�����y�d06nk+��ZE�L��V���|������
rDff��0�����F^Db���L{G*�������;����Y�4u��g���k���%{������[ L�GJ����v^���=���	��M��Dwv�;y�������9}���N���{����$��vw������3N�t���1��{����^��T`����~Q$��v?s��
���������]q��9�"��70'���[�����@����{(�����r�N3�%+�Z^?�	�r�/`�b.����,sPI���H�O	�/=s�����J�=��s��������$Vi����tf��9a��ZI�%8q.��;ISr�������L���z�>���``�M2,����e�>�]9;yG�)fW���DN�\K�]�~����t�|G�%q@#�)C���@bB
1X�W�(p7fECN����bU[�ws�����!�����[���7�<�*Ns�������7]��[peN�x����#w]B�|`\G�N[����}���w���^�W��
;�
��R�b����n4��A��G$c=�dg���6i����'@O�V��c6JJD�c^��w�3��!������2!_�j,��s&�w5�'w���bL���(c�������8Le)�����9��O0/��,O�.�����"Z���j�!����,R��7�
*
����q��#�\'���������t�w������nJ%�P��5R���[E�R����W0�^��a7��:qg#`nG�?a+,����$�5
��!Zx
�.`bVp�h��
������4�XR��~��{��(|��_1��
C8��eHYh]��{:��@�	8f�����>n�`�1�D##N���\j)1��&����*�)��X	6�u�)��!��3 ���F��l�~F������{�?GA�Qp5�r��Sb�.Z�K����p�>W�>�������G}��W#����l!@P�n�D4`�p
e�����t���Q[��Z'F�44S���4��nZ�Y�����)c�GWB��I��@"�@iU,[���aH^X/�*�z�_p���������G��wy_;��i]���j���������ubq����������LT`
���7@G9=�;�P�
{d5�!r:n���c�Wjxt�r%���� �D�Z�I��E��W�kJ.�(I0��e���������<:�Y��,9-���<�����I������]��:���.:{C�&*�9�C�h�ie�"�).�R5PO�\
:���BFiKWY~2.���8���~w�*����Z��w�$�?��tz�
�������$L�{��5^'���g(���N�����0wWq=	�����
@k�R���e<xNJ�W2d�+�^f����G�ieS�rTp��0�������I��$kL�iXY��-2���0��eRB�d�@$�H@YL����=U+
�Y:u��6)��P
�?�7g��S�D�T�i@�H�(s�
�V�v��N5�cfY��H�]2�y���\&���((�����8�-�I����?C&�LPh��O���S���&��2zQ\��b�d���1��ue���M8+�������#���J�E�eP��
��:e���[/%��W����*��[��
����������������������" ��\�������:i{�m�����n]���pvs�ny7mJ�b��|j�4����m?uU��h+KR���U��96�V�[�.-�z0�.}6$����7�X^���]�k�Z���+4�U��"�����~S��`�[��p��_#'��x��	��=�-��)��������3^��N�l0�PH�����b��HzG�)%�������F���>#�~�8m�9�h�z'���_|Y���]�$�Tz���{��[���'2����GP�5I�X�����"�Ql�=lR���tm��c��I�@��A8��gt9��u���D$m!LN��%�_�^�	ox_�����*&�{���JE����@��6,�D�O*j�n76{m?�2�����Z\����5%��P�-X��w�8o# �������Q0���]
�>�e��Q`wD�d�i����U�QU��+ag�R,���jj�	�_g�*�z�K!��n`�a�Z���>�P0"���0f�_�A�@Y��������8={/��C��"���{x�j���4���LSzGQ�|:h����WcQ���NfLA/�%���C�f�`�����X�M�{#!`�4�8���RIQ�����������Y�k�p�j��I�Kd�2)%���%�e���Y�&})��q)�D����@�L]�W��f*4�������=_A3ii���<���{u���!C�83L�T������k ���U|*��3�Aq$Z=L&�h`������M�������c�D����t+�7/�sS�x��5�,nRf���6���T��QCb���t]EU�02����HN�b
)|]Y�0>U��#4[��Z S�	�]�P��1�~Wbr�����^4�S63�+r�Y����U+7��_z]F7;�~��01�<����m^�����3��#3��&���h�����v��������F�v���}���V5��'�A-GJNp�yx^=�,����E2�|�-Y�����E]�9}9�X�@W��VJ�]JF�B�	r��u�yj����g���P���e2�+�2�����;:?,v�4���2��������x����~�P��1�B�����<�0'��:�����J�#`���U%m��3i�����TK�"���
32��y�����U���F5��#t���S�-��l�����x�������},B�3�~�c;����kF��l�U�����e��.���=�Z�~�}FV�<("�%G@�=�Cj�����'���W'��z��
���t�WgW0������X)#�uZ����uf�z�|��
�v�
,�	hF/-� %o�-�8���6�����_��[���3����D���>��B,_�xeg��<p1p��5�
��Nn=F��s��s�����}�|����-9���pj.A��RL>����4+��5��k��y&�
of����|+R�����7�~oGe�yv\����?��������vv�-e#��Q�/�S�.�!x�	�������,09����`��A�[�\{6�i��7)�/|y�����'9M	���U��R)k`����R����Bcubo���.��	2z,����.��J#����1���;zxCV�,�2L���(!t~o���>&��xJ�,X���L�qAZ�zcH���n%��[�3���M�.���M0�(/��p6��T���[]��a�A���7d��\^�x����v>?���7�����]+�|����n�j�*ei���t�vHEQ���V+��l��EN���F"m�h���>��9&�?�K�E@��zw�.L�$ >�S�n�=�����	T�\������i����2Od�Ae�|S~>is��	��JB�vip��[�����4��*�[�;�*x��O*������c��������-6%_�x�S/�g)=��$T69�\�s�[v��y�����)(i$?G=�R�N��o�3X�������*�c�^���~��dm����X.i�'`/��?k�)m�9�m�Xr���.������bv_EI��T���L|F��[�W��8��5�u����f�PkY��_�k8�5I���~p��]����5�&0��^�Rk�r��*��c��`�(�%^�<|��=��l�veh��*���=�)��Z;iAh��"t�E�B�hL�0	�A7E�����iG�eG�U�2���zCi:���r��'�@���VUj�29��o��ELUE$$����vNT�:�9832��~�2��T_�0=��@Q���dA<(S)����cE����
r�.�K@O^�b��dxb\|	Pe�8����)�m�"I�'r9���������9���I����r!��EZU��Me���2��64�7��h���o��3C��R�GZ�)�T�K�n��j��[��6ix�L�,L�����2�/��_Bv�0)�=)�Hn@m���S�*D���a����	��TC�^�� z�|��C�qw��I����
�P3�)�9y%K�V�\�C�4���*�H�����J[����~��
W_
���h�������������Or�D�N�$oE|�j�]�6�q��2w��R}��c���.���I��+N7�i���$��#0�����N�W�������3[�����a��	y�\�K�:�e�5��6�!\e���aSP6(�H��������;Ak�B#8�w�5Fa�Ru�D��
���mI��4
�X�	�`����(u�����"�f"�^�Zi6������M�Al��z�I�W$����~��E����[m���\H����9#w/���i��eh�8���#{�a���q"�-x��Z����
M�n:5���c��7x%C��n=Uc�OB
����i�dU��_�po8t7+-�@>���O�Yq&�&�F�{@��L*��g�
K�P�OdB��)E9��Q���>���lY������x�]Y>�5HI*���[�267��B��
	G�V�#����lMeZZ��K!�Q~9���s�<�������|6��$������"�^�K���x5��h�e���!.G(����FZh�$�@��P���7#N��`��3UM�(Q��:�����a^
M.�O1
g�w!��B�z�-���7.w���)2��������2I�-Jvh[bX�I����(\V�t�m��<(�4*��i�!�t�a������5;$��!��t�l������8�`�c�WW�R_I�����]u/����4�dj�w���hw�n��]7��0�����������RpleV$hw?.)}���V[�<��y�F��n2���1{������7�V�����'%P���Oh�P���X�0BH������I]�P
V��|E},l"�����'��6�a��������E��bc���^U��E����$dt�8=T����jjMUU�
���-�R6L��-S��6��(jNm [rm�[��g���cg�qY�����H;=��'Sh�������w�:o��-���A�*��
����P7���&���-�{<HV(���R������ �5�t�^GP._����xr�����j{����Y%�cI=}�`���:R����
5��FGVv�(
�A�Ts"M����L���*����������`��o�3mL�?kO����x��t��"����g0P��l+C^�o&S����$z�1x*9����.f�
������L����K�+Ry������[��+��]�2^��I�9��l��.o!�r����J�Mvq�1��y=�,�-�A���@&{�?D�jV�Y���E`%�S����GS��V\\u_�\�bV����z�H�-�`�A�"TIsKm�����(./4�Nt-�,������q����X,{9����\aSv4�#�F��GQ�P�2������b�*��r�v����N��B�\����t\o���������e:�Ssl��a}�.�*��_�n�������c<��A�L{��Q$
`�(B���t@6y�cW�9B���"��^}w|�"�j���n�?�����������t@I�1I�8���}tR���C���V�w���n�n��n����}�	�jR��`��w�����j���-��i����� dJI��l�>��9{{���RV��$�[�q��l"r\��s`i���s��Y��V��.�f���l���fy:�F�	
��8�	�F�����J�0�%�sL�,n�TJ���f�&�(r�x,7T��� cJ��Ql����I�k�n�7��P����{�4)a����M,�����|�p.�e�9�ss���(�_�dn��7��z�P_���92u�r��l���2��B�[@������)�PT���oQ�Kj������`����(�{�e�[IL9s�����9�i!�<���{�"�-�B'�7�|$���k����J���d������*��^��3c�E�z��C�����	��y��|�wL���!�~��=���q)e1U�g��V�]/��t�/N���������4�;/d0a8���#h<��vF�6<xSASC���O�!e9����P8��F�m(����=�Y����F�����Mv�;���w�2�F�M��X�7�a,]�M��p���9T��"��A�gT�j��5y��\��+���fOs����N��%��^�M��e��Z�~���#�gh�>���N(����tV_�4-���������N]d�{���z����HM��N��L�
�����G�+���W|���jX�Ecu���"Yj�]�����Lc��
�{�T�%��6��Oe�d#O?��\^����?��������%&���0B�d����1����+<~
��gY���%���YKg���@d�������+�x��O B/�p�[<J�%�m7�HzQF��3Z������K/�#%Ir�b;:~�}R���fGw�vt7��P��U���3�	��� 
�~t?	i�v
7;J�[���	1�����nX(�����^bv�H*��&.��r�kq��v=3i�F�OB��O��G���A�N�(�7z#f)��3I�t���.@���!O�e�P����3�HMN�DF���`Wz���6r�8>}����D�Z=%D�g����F��w�D��=D3��u�%�N�������d@]@�GM���������f��T7&o!�L�Y���/���VWe��*���u���1����[M�Y��������@���T�O��g����?�%�\�]��\w_�&��R��8����f�SwY��4�Y'>%�����
�����T=������/���<�J�%5OW�k��M�����~L��Ab;,9��V����2��xR��E�����{�F���9���������Zv��L�'�Hi�����?@z�������)%6�$Q'y<c���d^p�^��BN�P_C���
q��;m�h�-��[��|�#l��209#�2���=�����O'��r��	�S|1���0���Y�]������nbb���5G
���xM8J@���t�%�&7����:G���;��q	C�1��)��^����E��"v�nl���i��'�zK��2q���.��^�&z�#1Y�F�������L�7==��9��97;��K��]��v+9T��Na,�<.����H����}*�x�*�
)?��k���!�K~�����u?������1�����<t�<��,@)�����`�B���U=��2��~�Q�Y��r>��;����%��[���(K��X
5�2/������z�,�MD�N�'����-^%��m�?f�o����2���E��
e,9�H��KT������f�Z��D4��gS<�_M-�E��-�Tb�5qv��4���dh�rN:��s�����������E�p?�z=�r�L+g��=)�r�6����YC�UZ\�e
�����
|hH� d�����������H�����u���3X+�A�ye�`�,�i�5���LIV������uh��/��q��WN�wN��N���3];Y�N���ty�*pA���r@�������nw���vf{���gm���d�<�j������la1���0������B�$���_���m�}zv�6/�������WrG�Ry@�4L�[�� ��8�<��. 6�Wk(�{���=
�����*P��e?���'�
�z�$�����x���4('l�iQS#�����q��1�|�i�1i$>������VI���c]ZX��AW>e\b�R�B�u���b�.�d�pb��F�l������K�8_�V+��<'<�xA��1�H80,� �u�(��� 7����0�P�"x���h���^w#R�`s�S��P�_�s6`z�8(�)F��:q�}]�D�zN��"�t_�/�����qr�I�+:�J� W���j4�n�_����z�'�[[���+�ju~/+�������-���J}Kl�GC������p��z�8����Ux�i�w����[o��ng�m@e2K`\�����}���?O�a��{ +�sk,�i8�_P��D
~��k ���/og����A�Sf|�`j����l��+������ipAz�z���pAyr����7�LLm$-	��<;��FBn��G�G�H�L�=� o&6�t��b4����`��S
{(q��8vUN��u�7��*��0�	q6�b��Q��hz!P����["�7>�<1��6�.�\z����y1�F�me3�.���)ea'P(�,��[D���$d��S�cD"V*m�c�)I��)�%|*7�����d�����</7���5P��k�(;���s���[*hW��jO������
oeN2��~B�o���7�-��$5��@��W�� 7(e���h�������j�$!��a����sI�����B��%n(�=MT��?��_���?������S&����k������h�������&�R���I,C��/�e�4P]�����Y�b�xE�D�D���i��<�����"�Z|���hG���i�Q�������='*�c,f�2���2����"�i���C����p���i����uY\���S��^�2	���U�R�N���s5��\��V	��.����@��u�S��.9b��9���~#n�\����L�	y�9M����iV�9������2b#
]��G2"(O��yq���IB��R�(�|vc44	�@��o���,6$�G�2���s�9��1�j��K�z�i@mP�����(}Z���n���h�3�u=�c���r��tW��GHB�*��d;��z*h��������G�H�o3xCQrY+*�������V���$�$������CN�E��A��l�Y=#����I
�Hm��g����8���g����zW����[����?��M�9��t�3���� -�`/����}
z��z��Rg���m8�?��ul����pm%qd�=*8,�|w��������B�Au���
(�_6E��-~��'{t:P�!duf������$��K��,Q�x���M?/����������A�m�"��i��a,�3��#����T�WR�*�b8`��X�O=�����
+FH��f9�z�F����B6�w�!���i6�#����"�����r!��A {M0�5Sag
Q~4��7��m�]Qd
4p�����CL/������UqII��zFgY��<P::�Ra
�A�J�#c��y)������:�������S�.�C�i_8i��xEw�\$g���m��$([m�QI9b��w����������� �~���_l�u�`R
TN'�����=������Z����q����&��0"2�[E���DM������^�����a4�����6�O|4a�tz~r��$!���_���*u��S	(��W,���>�^�c����8�*]^������X���Gn>g�������uu}��m�����s�Y��7M.t��J������dT�a"���J)�I���[�u��5����	vY��$�U��;`k�0}c���e�!s����R�*��=n^�g�����&�
n$-���x��K�y1h������"6Dh�86���lk.�S�m��
$�x
�M�O"Pq<�i�E������i�9�2�,83�����E��2��f�t�u>/P��A��o?�6Q�c���`L���=���'��D�/I�^�0L��R��o0������WoO�nZmqH���5�2�S��M������<�><N)�����V�!A�v�gL�`�������N&�#@�����)��������DY��������b>��Ye?���35�ee�t���
����Z�]�?E��;AOQ���27���1���\�	�l��7�=@�(>1���h��'��X��1c�KQ�aU�O������ Ut`JY���[q�Y�'����K���y�H���DWoP5B�L����I��~�����,m\���G�p���+��3I���Mu�tpu�.lH�\����V�)��|��r�-U
����2�"�*��;�(e4��+?�����oG��j�!wf�~��A�F�(��'���h�c�����7���\��2���8|�Qv�,v�V}�y��l4%���De���4�� |����LG�L���4j��������������AL�hi������`������*��+��<r��������\��E����z��
�MLV#pw�A9�3�P:
���9g-e���a�����cx�L>�`�����g��h.AV77��LnI&i1L���� 	3��v�@��=���T��`bgIQ��x�������/�/����8���E����E�RP��A��!��
2��4��x�����N=(S������8��������K+���-���ikfI�|v���w.�������]����5���-����2RY�9�f�������}�~��
*�07�*y�kB#d�,<�*��:��SV@;���@���E.��{�Z�m�P���wK��	uh���w�|C����[�m�_��|�e���>�@���+�����l�|�g�i��T��J^���P�\J����
���D�fG&?���%P7���m�F���b�U�C:~g� V{�aj��V�-
'd��u6e�sK���P������TS��[������s=��Q����[mqyQTT���5-�*u0�J'�v���B*���W�H���$����/��H��(���Ix(��8��'���3������^E���G�C�z�R���(�A/����P��^������&k�������k�����@�^����t���ipe,���(��X8�?ubC��"���O�C�h��I(�����@

t}�<#�t;�f�U��D�E�TF��(Vck{���Y�be�gQ��E�W��N4�m>'�c.�-W;��k'�rK�A�ga�w`o�?�����?-�����D�8�
I��4[��^������40�t����� S�]D�]2���
���sW�\CI����d��N��3k��k+��/�k+�g����;y?��� ����,B�
(;t�E��89��!��z��r�E�+��Z��f��~�o��l�u^���KpG����C��G��6��`gB�k)��QB[b��l�3�2!U"��$X��%� C����J\���=��Z���m��]Cdce'��YK��&~����M#Y&�])���Tr�2��&J�Q�%�Ao���3&�S���~/��S��:��u�O�TU#|��@�lP�����.���^�h=M6�O*&��p�uQm��C�P�:�NhYd1rGQ�	'�E#�&��@���,e�)z?�������� �l5vu�����E�^�
_5sJ�36/�2.����#
Z�*f~�GL���.�6�7���-�U�����O"�<g��������P�k@��<y�J&u-����]m�c{q�e?e���D�6\�R���[���zI���#<e��W+qt/\����@/1�<����KA��O�jT���0+c�$��3�f��c��tF]N$\���r'p����8�
�NN�"���l����-��}	�~6�i+��E�pu��X4+���&��Y� ��T��Y���mL���Fx����8<��2���#S
����8�01���R�p���}���4������[Ni��wv�^P�������v�����ak��%'�����M0����y��W��i����w���	g���V-��7�qC����J���V`|�BZ9-�X�V��Oy��v�<�l�@��xz�F�2Z���;I�����*b���_~�}��e�vv�<H(X��f��c^_�#����[rY���[����,U����6�Gh�	�x�d�o�����������<��V][�����@���4Y��5��k��;X��Y���,j0^L��12
���N[�
�$�n�n����F�\C����/k��������E��u]�hH��8h�8���4����}v�:b[t:����\j^��$=�.y,�q,�@:{�Q���L��t-�	���
�{�.#^�''v���f��v��"}m�~�"��$�4SO��oj�����H������@���a��U�C��G7c�u�(�+3/���l�1C�tZO(�����x��!�������jB��uaG���1(�UI3�-���hZA���w���FG���id|Lm�����9H�:�W������4�[l��q����X�������G��m"����`�]�����HK���,��y����>4�A����X�X�*�d{ok�`��v���=����PHS!�9z��f.���I�c���aGi��o��~
�&r5&c��qry�"�V�s�������\(h��Vs��M���$[vG8~�����]�RG�"~i��&~2�������T1�����j;���k������
��!���]�`)h�"�h<dQ���QJ�lv��������Y��p�.�Z^�7_�T�)����>���@%�k ��+�L;b�c�6��&o)�����L�b#FX�j�}�FS�&�����G�j��17cA�X�x�����nU�����KQ
B8�r+%������<"�}��`��V#�W�?�:���&|�q���&�@M{<^�g�M�M�N�*�K6��|����wp��FG���ob������rlH��]��S�q�N��t2�����L�7��jU���_����m��������{
�m�z�q�U���0�p������jw@.0^D
���yu�j� w(bg�|#�)���b��B�G�Q��Qb{��e���c�&L��KU+(�0�O��1��	e�T:\{��oq��a�!���g�xr�w�I1D���nWv�V�a��^j����.�&�'9��z�!u 1#+������oU[��������)L��:���8|��Y_�?�w�Nl��y��O��NU�Q��1�������C�CBMC��yH`����}�(%L-b.T���9sKJO���9?�h�����A���;�@!�"2��)4��6�H�oRkHGL���k��h����T���{x9�!�82�����D
�>�]�l�mt��5-���r\���OHM�vN�n������=^��`��������".�_�e��H%8�]�M�jH#����j�(Un,�h��;.�r��m�Z���������;��oY�s����b��c%���5p(���I}�#����@����U����bSt�
Hy3y�f���������.�i{vy���%;W����z[�B���-��~�Nl�{�� �%��L�	2�9`�����\��S�r�"��"x��^fc?�l�=�@�N����r����l^pw?~�����n���-��;�?�F_��ga{������o�
\�������a����<F������O	i�_|/2��3������'���$n�:5�g�s|4c�9������V�k���w�$:4�:������3�����y�^�B���;}k���:����j�2i��/�h��?�~R�m�����
7r���	Q�^��
��~*��S�6g���)�^r~������J�t�F;�5�A�i��b���"L*�0
�T@,���:�n�=�i�[���
6/������������e%�(AH����9�����g��5h{'��%�� �#\��?H�K���=�F�!��V[`��������M�5�A��������%���lVT	�i�0����D���Nnm��|�2t�����W��U�a�b����	 }�������`�j���cu)�����g��=4,+s��:����J�2|N��CB'�i=�J���$��|N��#��9V�"���Q������R
��B�$)�@ s��Mh�%��1�Yh
�o��&P�#����w��d �& 2J����]�en���W?�f�g�����6�S�"
�������xL��(������mZe9��"�3���fYX�D?/�j9g�.���.�4����G��(��W�h[��[�mo�Z0b�\����������X���t��{4�G�Xb�](��\RX^��%@��S��
���?!����Y����,S�7�6>���9�0�|�����c�h����)���Qx����Gk=-S!c�/��5���(�>���w��l�x������CV��U�������Q!��a���[��1K��W����k��:?r�3�����[�Jr�����E���8�6�[
*we�;C;�'���>�S�Y����)��&��h6����?�CX8b�$rv��4���W/>��	�����]����������	l��A����z��T�XPh��3�^�����S�g�"�\N�����<������H8�n�{u��$mb�8��YT�%1��h0�7��Nf����"�0��s6J&�l?��?}��E��>��|�A��G���<�S��<�1�0D�
��0�EEdd����^��������m�E�#�%�	*�,F!j8�*~��pr�=r�,�����80#��`���+�OA��?6X���1��(���v���mo���I_!�d�Ka�f�1�>_��G�T[�����oP��~s;cd���~����{��1��4�� $��IM�b���n��v�w�x������"��#z=�L�����+�����	Oi�{[�������4w�q����U�4*�2��P���c��cj-U��Wk~��?}���
�oi�DXZ��OB.Gm�i���i"�|y�5? ��+����Y�S��"T|����^������Sv���F���_����=2��?��?���\��M��9�S�����9��i��1D���0�	]�4N��������>���_f]�h!��[�>�
������B#�3�?���'<-�p;���g0�|�2-c�yl�0����~I�}���Z��N�h"&�x�w�����0�~����9	���(��'�X|���f/��I ��G�#�7D8��lv��p��)D��T��D8�F��W����f0��l~��������`|�cp����Y�������'�pt����;�[�{t���QwF��Z�^^_[?Z��-D,��
#26Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#15)
Re: poll: CHECK TRIGGER?

On ons, 2012-03-07 at 12:31 -0500, Robert Haas wrote:

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql. It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

I had mentioned upthread that I would like to use this for PL/Python.
There are a number of code quality checkers out there for Python. I
currently have 3 hooked into Emacs, and 2 or 3 are typically used in the
builds of projects I'm working on. All of these are shipped separately
from Python.

This leads to the following requirements:

* Multiple checkers per language must be supported.
* It must be possible to add checkers to a language after it is
created. For example, a checker could be shipped in an
extension.
* It's not terribly important to me to be able to run checkers
separately. If I wanted to do that, I would just disable or
remove the checker.
* Just to make things interesting, it should be possible to
implement checkers for language X in language X.

If it would help, given an API (even if only in C at the moment), I
could probably write up one or two checker function prototypes that
could be run against the PL/Python regression test corpus.

#27Peter Eisentraut
peter_e@gmx.net
In reply to: Albe Laurenz (#24)
Re: poll: CHECK TRIGGER?

On tor, 2012-03-08 at 10:49 +0100, Albe Laurenz wrote:

Actually, I did when I reviewed the patch the first time round.
I think that the checks implemented are clearly good and useful,
since any problem reported will lead to an error at runtime if
a certain code path in the function is taken.

Shouldn't the validator just reject the function in those cases?

#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Peter Eisentraut (#27)
Re: poll: CHECK TRIGGER?

2012/3/8 Peter Eisentraut <peter_e@gmx.net>:

On tor, 2012-03-08 at 10:49 +0100, Albe Laurenz wrote:

Actually, I did when I reviewed the patch the first time round.
I think that the checks implemented are clearly good and useful,
since any problem reported will lead to an error at runtime if
a certain code path in the function is taken.

Shouldn't the validator just reject the function in those cases?

Validator check syntax only (and cannot do more, because there should
not be dependency between functions). But it doesn't verify if table
exists, if table has refereed columns, if number of expressions in
raise statement is equal to number of substitute symbols ...

Regards

Pavel

#29Pavel Stehule
pavel.stehule@gmail.com
In reply to: Peter Eisentraut (#26)
Re: poll: CHECK TRIGGER?

2012/3/8 Peter Eisentraut <peter_e@gmx.net>:

On ons, 2012-03-07 at 12:31 -0500, Robert Haas wrote:

I might agree with you if we had more than one checker function, but
right now we are proposing to implement this for PL/pgsql and only
PL/pgsql.  It seems to me that we can add that when and if a second
checker function shows up, if it still seems like a good idea.

I had mentioned upthread that I would like to use this for PL/Python.
There are a number of code quality checkers out there for Python.  I
currently have 3 hooked into Emacs, and 2 or 3 are typically used in the
builds of projects I'm working on.  All of these are shipped separately
from Python.

This leads to the following requirements:

     * Multiple checkers per language must be supported.
     * It must be possible to add checkers to a language after it is
       created.  For example, a checker could be shipped in an
       extension.
     * It's not terribly important to me to be able to run checkers
       separately.  If I wanted to do that, I would just disable or
       remove the checker.
     * Just to make things interesting, it should be possible to
       implement checkers for language X in language X.

But you propose some little bit different than is current plpgsql
checker and current design. You need hook on CREATE FUNCTION probably?
It's not bad, but it is some different and it is not useful for
plpgsql - external stored procedures are different, than SQL
procedures and probably you will check different issues.

I don't think so multiple checkers and external checkers are necessary
- if some can living outside, then it should to live outside. Internal
checker can be one for PL language. It is parametrized - so you can
control goals of checking.

If it would help, given an API (even if only in C at the moment), I
could probably write up one or two checker function prototypes that
could be run against the PL/Python regression test corpus.

sure, please look on patch and plpgsql checker function. Checker can
be any function with same interface. Some PL/Python checker can be
good.

Regards

Pavel

Show quoted text
#30Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#26)
Re: poll: CHECK TRIGGER?

On Thu, Mar 8, 2012 at 4:54 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

     * It's not terribly important to me to be able to run checkers
       separately.  If I wanted to do that, I would just disable or
       remove the checker.

Does this requirement mean that you want to essentially associate a
set of checkers with each language and then, when asked to check a
function, run all of them serially in an undefined order?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#31Peter Eisentraut
peter_e@gmx.net
In reply to: Pavel Stehule (#29)
Re: poll: CHECK TRIGGER?

On tor, 2012-03-08 at 23:15 +0100, Pavel Stehule wrote:

But you propose some little bit different than is current plpgsql
checker and current design.

Is it? Why? It looks like exactly the same thing, except that the
interfaces you propose are tightly geared toward checking SQL-like
languages, which looks like a mistake to me.

It's not bad, but it is some different and it is not useful for
plpgsql - external stored procedures are different, than SQL
procedures and probably you will check different issues.

I don't think so multiple checkers and external checkers are necessary
- if some can living outside, then it should to live outside. Internal
checker can be one for PL language. It is parametrized - so you can
control goals of checking.

What would be the qualifications for being an internal or an external
checker? Why couldn't your plpgsql checker be an external checker?

#32Peter Eisentraut
peter_e@gmx.net
In reply to: Robert Haas (#30)
Re: poll: CHECK TRIGGER?

On tor, 2012-03-08 at 19:19 -0500, Robert Haas wrote:

On Thu, Mar 8, 2012 at 4:54 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

* It's not terribly important to me to be able to run checkers
separately. If I wanted to do that, I would just disable or
remove the checker.

Does this requirement mean that you want to essentially associate a
set of checkers with each language and then, when asked to check a
function, run all of them serially in an undefined order?

Well, the more I think about it and look at this patch, the more I think
that this would be complete overkill and possibly quite useless for my
purposes. I can implement the entire essence of this framework (except
the plpgsql_checker itself, which is clearly useful) in 10 lines,
namely:

CREATE OR REPLACE FUNCTION pep8(src text) RETURNS text
IMMUTABLE
LANGUAGE plsh
AS $$
#!/bin/bash

pep8 --ignore=W391 <(echo "$1") 2>&1 | sed -r 's/^[^:]*://'
$$;

SELECT proname, pep8(prosrc) FROM pg_proc WHERE prolang = ANY (SELECT oid FROM pg_language WHERE lanname LIKE '%python%') ORDER BY 1;

I don't know what more one would need.

#33Robert Haas
robertmhaas@gmail.com
In reply to: Peter Eisentraut (#32)
Re: poll: CHECK TRIGGER?

On Fri, Mar 9, 2012 at 3:15 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

On tor, 2012-03-08 at 19:19 -0500, Robert Haas wrote:

On Thu, Mar 8, 2012 at 4:54 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

     * It's not terribly important to me to be able to run checkers
       separately.  If I wanted to do that, I would just disable or
       remove the checker.

Does this requirement mean that you want to essentially associate a
set of checkers with each language and then, when asked to check a
function, run all of them serially in an undefined order?

Well, the more I think about it and look at this patch, the more I think
that this would be complete overkill and possibly quite useless for my
purposes.  I can implement the entire essence of this framework (except
the plpgsql_checker itself, which is clearly useful) in 10 lines,
namely:

CREATE OR REPLACE FUNCTION pep8(src text) RETURNS text
IMMUTABLE
LANGUAGE plsh
AS $$
#!/bin/bash

pep8 --ignore=W391 <(echo "$1") 2>&1 | sed -r 's/^[^:]*://'
$$;

SELECT proname, pep8(prosrc) FROM pg_proc WHERE prolang = ANY (SELECT oid FROM pg_language WHERE lanname LIKE '%python%') ORDER BY 1;

I don't know what more one would need.

Well, I agree with you, but Tom disagrees, so that's why we're talking
about it...

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#34Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#33)
Re: poll: CHECK TRIGGER?

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Mar 9, 2012 at 3:15 PM, Peter Eisentraut <peter_e@gmx.net> wrote:

Well, the more I think about it and look at this patch, the more I think
that this would be complete overkill and possibly quite useless for my
purposes. �I can implement the entire essence of this framework (except
the plpgsql_checker itself, which is clearly useful) in 10 lines,
namely:

CREATE OR REPLACE FUNCTION pep8(src text) RETURNS text
IMMUTABLE
LANGUAGE plsh
AS $$
#!/bin/bash

pep8 --ignore=W391 <(echo "$1") 2>&1 | sed -r 's/^[^:]*://'
$$;

SELECT proname, pep8(prosrc) FROM pg_proc WHERE prolang = ANY (SELECT oid FROM pg_language WHERE lanname LIKE '%python%') ORDER BY 1;

I don't know what more one would need.

Well, I agree with you, but Tom disagrees, so that's why we're talking
about it...

What Peter's example demonstrates is that you can apply a single checker
for a single language without bothering with any common framework.
Well, yeah. What I've wanted from this patch from the beginning was a
common framework. That is, I want to be able to write something like

SELECT check_function(oid) FROM pg_proc WHERE proowner = 'tgl'

and have it "just work" for all languages for which I have checkers.
You can't get that with a collection of ad-hoc checkers.

If we're going to go the ad-hoc route, there seems little reason to be
considering a core patch at all. Freestanding checkers could just as
well be independent projects.

regards, tom lane

#35Pavel Stehule
pavel.stehule@gmail.com
In reply to: Peter Eisentraut (#31)
Re: poll: CHECK TRIGGER?

2012/3/9 Peter Eisentraut <peter_e@gmx.net>:

On tor, 2012-03-08 at 23:15 +0100, Pavel Stehule wrote:

But you propose some little bit different than is current plpgsql
checker and current design.

Is it?  Why?  It looks like exactly the same thing, except that the
interfaces you propose are tightly geared toward checking SQL-like
languages, which looks like a mistake to me.

no, you can check any PL language - and output result is based on SQL
Errors, so it should be enough for all PL too.

It's not bad, but it is some different and it is not useful for
plpgsql - external stored procedures are different, than SQL
procedures and probably you will check different issues.

I don't think so multiple checkers and external checkers are necessary
- if some can living outside, then it should to live outside. Internal
checker can be one for PL language. It is parametrized - so you can
control goals of checking.

What would be the qualifications for being an internal or an external
checker?  Why couldn't your plpgsql checker be an external checker?

plpgsql checker cannot be external checker, because it reuse 70% of
plpgsql environment, - parser, runtime, ...

so creating a external checker is equal to creating a second plpgsql
environment - maybe reduced, but you have to duplicate parser, sql
integration, ...

Regards

Pavel

Show quoted text
#36Tom Lane
tgl@sss.pgh.pa.us
In reply to: Pavel Stehule (#35)
Re: poll: CHECK TRIGGER?

Pavel Stehule <pavel.stehule@gmail.com> writes:

2012/3/9 Peter Eisentraut <peter_e@gmx.net>:

What would be the qualifications for being an internal or an external
checker? Why couldn't your plpgsql checker be an external checker?

plpgsql checker cannot be external checker, because it reuse 70% of
plpgsql environment, - parser, runtime, ...

Well, that just means that it'd be a good idea for that function to be
supplied by the same shared library that supplies the plpgsql execution
functions. There wouldn't need to be any connection that the core
system particularly understands. So, like Peter, I'm not quite sure
what distinction is meant to be drawn by "internal" vs "external".

The thing that really struck me about Peter's previous comments was the
desire to support multiple checkers per PL. I had been assuming that
we'd just extend the "validator" model in some way --- either another
column in pg_language or extending the API for validator functions.
Neither of those work if we want to allow multiple checkers. Now,
I'm not at all convinced that multiple checkers are worth the trouble
... but if they are it seems like we need a different system catalog to
store them in. And the entries in that catalog wouldn't necessarily be
created by the same extension that creates the PL language.

regards, tom lane

#37Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#34)
Re: poll: CHECK TRIGGER?

On Fri, Mar 9, 2012 at 3:33 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

If we're going to go the ad-hoc route, there seems little reason to be
considering a core patch at all.  Freestanding checkers could just as
well be independent projects.

I completely agree. I think there is little reason to be considering
a core patch. I haven't seen any convincing evidence (or any evidence
at all) that being able to fling checkers at a large number of
functions written in different procedural languages is an important
use case for anyone. I think the vast majority of checking will get
done one function at a time; and therefore we are gilding the lily.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#38Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#36)
Re: poll: CHECK TRIGGER?

2012/3/9 Tom Lane <tgl@sss.pgh.pa.us>:

Pavel Stehule <pavel.stehule@gmail.com> writes:

2012/3/9 Peter Eisentraut <peter_e@gmx.net>:

What would be the qualifications for being an internal or an external
checker?  Why couldn't your plpgsql checker be an external checker?

plpgsql checker cannot be external checker, because it reuse 70% of
plpgsql environment, - parser, runtime, ...

Well, that just means that it'd be a good idea for that function to be
supplied by the same shared library that supplies the plpgsql execution
functions.  There wouldn't need to be any connection that the core
system particularly understands.  So, like Peter, I'm not quite sure
what distinction is meant to be drawn by "internal" vs "external".

internal - implement in core, external - implement in extension.

This is history about this feature:

a) I wrote plgsql_lint, and I proposed it as core plpgsql functionality
b) there was request using some different then GUC, and there was
first check_function
c) there was request do it with more user friendly - there is CHECK xxx
d) there was request for support multiple checks, there is CHECK xxx ALL
e) these features was reduced - step by step,... but really important @a

Personally I think so any form of plpgsql check is big step against
current state. We can move general check functions to plpgsql space,
and it can be good enough for plpgsql developers. It is not user
friendly like CHECK FUNCTION is it, but it has important functionality
- checking of embedded SQL without execution and without dependency on
executed paths.

I cannot to move plpgsql checker to extension, because there is
dependency on plpgsql lib, and this is problem. If I can do it, then I
did it

The thing that really struck me about Peter's previous comments was the
desire to support multiple checkers per PL.  I had been assuming that
we'd just extend the "validator" model in some way

I tried to do it, but it is not practical - a interface of validators
is volatile now - it is different for functions and for triggers, and
it doesn't expect returning anything else than exception. More - other
new functionality can increase complexity of current plpgsql
validators. So this is reason, why I designed new handler function.

--- either another

column in pg_language or extending the API for validator functions.
Neither of those work if we want to allow multiple checkers.  Now,
I'm not at all convinced that multiple checkers are worth the trouble

I don't see a reason why we need a multiple checkers - checkers are
parametrised, so there are no real reason, But what statement will be
maintain this catalog - CREATE CHECK ? You need DROP, ALTER, .. it is
lot code too.

... but if they are it seems like we need a different system catalog to
store them in.  And the entries in that catalog wouldn't necessarily be
created by the same extension that creates the PL language.

This looks like real overhead. Without user interface - like CHECK
statement, what is value of this?

I am skeptic. Can we go back and can we solve a checking just plpgsql
- it just needs integration of plpgsql checker to plpgsql runtime. Any
PL can have one or more these functions. And when we will have a
adequate user API - SQL statements (CHECK statements or some else),
then we can create a new catalog. It is strange do it in different
order.

Regards

Pavel

Show quoted text

                       regards, tom lane

#39Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#38)
Re: poll: CHECK TRIGGER?

On Fri, Mar 9, 2012 at 5:09 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Well, that just means that it'd be a good idea for that function to be
supplied by the same shared library that supplies the plpgsql execution
functions.  There wouldn't need to be any connection that the core
system particularly understands.  So, like Peter, I'm not quite sure
what distinction is meant to be drawn by "internal" vs "external".

internal - implement in core, external - implement in extension.

[...]

I cannot to move plpgsql checker to extension, because there is
dependency on plpgsql lib, and this is problem. If I can do it, then I
did it

I don't object to having this feature live in src/pl/plpgsql, and I
don't think Tom's objecting to that either. I just don't think it
needs any particular support in src/backend.

I don't see a reason why we need a multiple checkers - checkers are
parametrised, so there are no real reason, But what statement will be
maintain this catalog - CREATE CHECK ? You need DROP, ALTER, .. it is
lot code too.

If the checkers are written by different people and shipped
separately, then a parameter interface does not make anything better.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#40Pavel Stehule
pavel.stehule@gmail.com
In reply to: Robert Haas (#39)
Re: poll: CHECK TRIGGER?

2012/3/9 Robert Haas <robertmhaas@gmail.com>:

On Fri, Mar 9, 2012 at 5:09 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

Well, that just means that it'd be a good idea for that function to be
supplied by the same shared library that supplies the plpgsql execution
functions.  There wouldn't need to be any connection that the core
system particularly understands.  So, like Peter, I'm not quite sure
what distinction is meant to be drawn by "internal" vs "external".

internal - implement in core, external - implement in extension.

[...]

I cannot to move plpgsql checker to extension, because there is
dependency on plpgsql lib, and this is problem. If I can do it, then I
did it

I don't object to having this feature live in src/pl/plpgsql, and I
don't think Tom's objecting to that either.  I just don't think it
needs any particular support in src/backend.

I don't see a reason why we need a multiple checkers - checkers are
parametrised, so there are no real reason, But what statement will be
maintain this catalog - CREATE CHECK ? You need DROP, ALTER, .. it is
lot code too.

If the checkers are written by different people and shipped
separately, then a parameter interface does not make anything better.

ok - it has sense, but it has sense only with some "smart" statements
(like CHECK). Without these statements I have to directly call checker
function and then concept of generalised checkers has not sense.

Pavel

Show quoted text

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#41Robert Haas
robertmhaas@gmail.com
In reply to: Pavel Stehule (#40)
Re: poll: CHECK TRIGGER?

On Fri, Mar 9, 2012 at 5:31 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

ok - it has sense, but it has sense only with some "smart" statements
(like CHECK). Without these statements I have to directly call checker
function and then  concept of generalised checkers has not sense.

I agree.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#42Peter Eisentraut
peter_e@gmx.net
In reply to: Pavel Stehule (#35)
Re: poll: CHECK TRIGGER?

On fre, 2012-03-09 at 21:54 +0100, Pavel Stehule wrote:

no, you can check any PL language - and output result is based on SQL
Errors, so it should be enough for all PL too.

But then I would have to map all language-specific error reports to some
SQL error scheme, which is not only cumbersome but pretty useless. For
example, a Python programmer will be familiar with the typical output
that pylint produces and how to fix it. If we hide that output behind
the layer of SQL-ness, that won't make things easier to anyone.

#43Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#34)
Re: poll: CHECK TRIGGER?

On fre, 2012-03-09 at 15:33 -0500, Tom Lane wrote:

What I've wanted from this patch from the beginning was a
common framework. That is, I want to be able to write something like

SELECT check_function(oid) FROM pg_proc WHERE proowner = 'tgl'

and have it "just work" for all languages for which I have checkers.
You can't get that with a collection of ad-hoc checkers.

Well, there isn't any program either that will run through, say, the
PostgreSQL source tree and check each file according to the respective
programming language. You pick the checkers for each language yourself
and decide for each checker how to apply it and how to process the
results.

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#42)
Re: poll: CHECK TRIGGER?

Peter Eisentraut <peter_e@gmx.net> writes:

But then I would have to map all language-specific error reports to some
SQL error scheme, which is not only cumbersome but pretty useless. For
example, a Python programmer will be familiar with the typical output
that pylint produces and how to fix it. If we hide that output behind
the layer of SQL-ness, that won't make things easier to anyone.

Yeah, this is a good point. I'm willing to concede that we are not
close to having a uniform API that could be used for checker functions,
so maybe what we should do for now is just invent
plpgsql_check_function(regprocedure). I'd still like to see the
question revisited sometime in the future, but it would be appropriate
to have a few working examples of popular checker functions for
different languages before we try to invent a common API.

regards, tom lane

#45Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#44)
Re: poll: CHECK TRIGGER?

2012/3/10 Tom Lane <tgl@sss.pgh.pa.us>:

Peter Eisentraut <peter_e@gmx.net> writes:

But then I would have to map all language-specific error reports to some
SQL error scheme, which is not only cumbersome but pretty useless.  For
example, a Python programmer will be familiar with the typical output
that pylint produces and how to fix it.  If we hide that output behind
the layer of SQL-ness, that won't make things easier to anyone.

Yeah, this is a good point.  I'm willing to concede that we are not
close to having a uniform API that could be used for checker functions,
so maybe what we should do for now is just invent
plpgsql_check_function(regprocedure).  I'd still like to see the
question revisited sometime in the future, but it would be appropriate
to have a few working examples of popular checker functions for
different languages before we try to invent a common API.

ok, I'll prepare patch

Pavel

Show quoted text

                       regards, tom lane

#46Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#44)
1 attachment(s)
Re: poll: CHECK TRIGGER?

Hello

2012/3/10 Tom Lane <tgl@sss.pgh.pa.us>:

Peter Eisentraut <peter_e@gmx.net> writes:

But then I would have to map all language-specific error reports to some
SQL error scheme, which is not only cumbersome but pretty useless.  For
example, a Python programmer will be familiar with the typical output
that pylint produces and how to fix it.  If we hide that output behind
the layer of SQL-ness, that won't make things easier to anyone.

Yeah, this is a good point.  I'm willing to concede that we are not
close to having a uniform API that could be used for checker functions,
so maybe what we should do for now is just invent
plpgsql_check_function(regprocedure).  I'd still like to see the
question revisited sometime in the future, but it would be appropriate
to have a few working examples of popular checker functions for
different languages before we try to invent a common API.

here is draft

I removed all generic structures.

Regards

Pavel

Show quoted text

                       regards, tom lane

Attachments:

plpgsql_check_function.diff.gzapplication/x-gzip; name=plpgsql_check_function.diff.gzDownload
#47Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#46)
1 attachment(s)
Re: poll: CHECK TRIGGER?

here is draft

and there some cleaned version

Regards

Pavel Stehule

Show quoted text

                       regards, tom lane

Attachments:

plpgsql_check_function.diff.gzapplication/x-gzip; name=plpgsql_check_function.diff.gzDownload
�er[Oplpgsql_check_function.diff�\�s�8��,��N"Y��#N�����r�]����5;�E���g���ov����8R���������%@�����
(~0��v{d���co[��o:��0����~a��mA����{��y���y��;�=�c;�^��o���u�7���Z��k���;h�&���3x��������N?����~��+I�������;<����Y�r�D���+����	�r�[g��F��1wo���]��e1K���������M����8v����{������Hp/�e���Xl{3�����M��(���t�
��'�������n��8����o1�8�3�[sX�`A$p�Gn�����$'������37��
���?�;7|7�S��Qg��	�/s��s7���4�S���W�%7bc�&q#�����D-�����h�t8�h��MSw")��XdS������!��-�S9�\�����^rb^���l4��|������7������������<b��M���N(@����y�p�nz�����z\6I�9�v�������s���y��*��W}������U����h��d���.�V2�v$4���L T��=�!=c�nz+����cA�N�a��K��O����V�\���q���;���w�Lv��e�x�^��@�����5�/|M7��c������cP#����W�S�������?sO~B5��@;��@�}v::%b������8�������|(�Q�z�����q��8��uT�����!�i���?�_z��rq�f�c����-�����|����;VX�eE�����i�v���^���9������������"�o��n��M�[����|jlh�wq��"�H�4�/N�?�6X�^��������Ud|�D@�mS�i@����y7����c[��I�y�jh(��wN�����O�}��bKG���7o��Eqr��$��*Nx�	����c�%G���p�d�����	m{�9_��@�Y=����������FG���h�{U"��������#z�[z���e}�H�� �2���r��!b���",2�O�����v�wPF��Z
I�/������<N�Y�9n:�.0��(��w
�����6�T����r�:G���B>SE�������*<�	"C�_�(����$�U
�])�E�Y�Cx2��@&�sQ�1I]�)M�A:2s3l�
��M"��Y�B�O*Q�ujrn=����w���{o�����SL�+��*�cy�q�H�9�C� ��� �
���GD��k�F��������Uz�j��
A�i6���m���t��:J6����*��|����aF6�0�j��{��0�����S��"�.��.
�%h�����/A��9��M�����klxg���1O(I3lo����0���t8�������9x�
}��<���hG������C�-��N�l�?N�E��]��Q��*O���A����v�1w���i���u��V�!�
�sd3�\5����-N�&�2V�IR�����@^��D��;�����?Cm1�g/OE�J���7����� Z;q
��3�Q��O��2lAw��'��'Ma	y\�����_�E��4r@�|�r��0�A{���0�4r�15��Q���:S�9��	���+��j&&�ADp��`�K^i��"��8�-��1DT�[�wn�QTc'��p������(^��"�gb-���o5�')v����`�`�Q��R���]n#%��BB�Pn�p��Z�{��z��y)���V����]�e��$f/�7K�4�%!���)h���0����\��2�;w���fi0��t���H����A���F��3�(�e��	�nB�`
��O-I��^T�fK�Fp`��@�������)���$�-a����I9�}��V��2�������-����)mi���G��V�����C�M�i������_
y�	�At�����������ZX���	� a+���8�������4��/���}|w1�ES�X�K�!�b����B������wKslo4��P�
t*qPJA*29��D��^d
��S�a0N�T��M��`����k���i��y��������)�]1��cJTl@�d����0���n��>	�t���U��`��C��������������?�r�W�����r�RI@��y�����v:�p��(>�,(�Rxv�����:8��Y������B�����d,OQ���`pQ���I3q���G���3�`|�R�)���m������q%^�s��������ji0GjMn��|)��M��k����en/!oJ��Y�Rj��}^l�C_�*�
I�ox&�e*P> �z����[�8��L��d%\�W#�,�w<���L��B����(���^���`xy:r�/N�W�����.�I��&d�`�"O����0ithI.
M����#���cv����������8U�[���F]:$�H��C����h���;8�9�����a�����O�����w����"?��&��������orJA���,�3L����8��FC�'-��q��E���K`G���|x�\��/�����yA���U���<H��&���.b]��+�0��gVd�����p7�f7��)�N�z8��C��/�����g�����)
c}���
a�x6j��{}?XgH���`hv��|��#��j�X�\��Yyb���� _x��Q��K�?������H�j����d�0���z����St���#s�������@��f��^��xT^�t���x�n�<�P[��3�q��'�q���1��3���O�P� ��}��"���d�+�W�-8�%X���K��P�XA��"<���������M{�z�B�V������F>�����z�i_Y��!J���4��t�*����`m�� ����/����@��*�����5!4��t�����j��^����P���E�*��Jw�4�"�G ��99������>)ECE�Mo��mW���*����b���z����!"Cim����8��f�L�U����Rh����.H�����s��,@����:i,�!��)D���S��>�J&;��V:�(%W�6�K�2
n1��B��=6���?���RX��Q�cE\+���:�c����N65�^��RE�f���"�n�N���z��Z����@>�fNTU�3<����`�����
A��5�(���
�X�D�j�F�,	�G���!��=�;����{�vd+j�S��hU��G���������+O�|W���<iO�l	���f�)��>|�Z��������7���2��1�m�r��x�?��_a��n�
�O�d���@�������6	�@��������"����gCy��!��
�o�(�P6����-P��>�	���J��DB�������VG��CZ��-&����$*�3l���	�6(��3�KU��6F.U5��������9	A�nx�[GK������U�J����E�U�����T�pKj��i�IxP��DI�$��9����q������x.;�4���:�
�	�Lp�U�,��|�,��Pu&E�������W%b�m�.	��j�]I5���a���,)/N�/<C��z*��0�����)�w$+��Hv��f��A�]6���>���+��Y�*��M#S��s���tn:�	|�����(�M�i���5��r!r�I�Dx8n ��R� (��Ntx�l��)(�`��K�~��x�Cb�"Q�#�������=�����4�/�*u��k ������bfm����33,���5�lV����P<x�!������bt'?��u|�|��
(FK�$��Ll�#������qKm��i��%~���
�JE-�D&�c��[���V�Q�<�H�^>�j(.\;�Vx�9�"�u1��_��$N]H�e*^
�$�E����E����4������By����} "�����V��?w�T4�%���L�t ����������)�7��8���V\������@�.�d�SLz0��*���v�W}E���LQb�8�����%��IlF��:xkO��p*��P��7)���i�U.�CvE�UI��>Od4v:�C1�r`��]�����v�T�W��A�8C������9���3����_�����Zx�I�G/??��ec����Y�R�H�q9X,���b�D^���/��nsV�:��JA���*�m�!�n�wM�z��,�e�jY�*o���wV��������}b%��|����(���u�������u�mj]@�i���uJ��u��{�Z1����>r�0*Ns��)�����F�Y��F�x�*�<{��C� ���8�p�>W���7�u1����Bn?�4�<��b=�W?������	w�xO����X��w3$�������K�$��R-v'�p�������2/<�P!)�����V��s>^F��R��O��V�j������8h�T��t�(�*/��Zm.���D�l����Q��&?[�^��2k��(��j�%c���5�mU	�d�@o��9��.��$*���	�#W��lT�����p�K�;�b�b���ka��|X��0�2�$5Ye�"��\�{����>��g�\il!BdC������y���&z���P@���w3X���SB!5SY��8j����DV��`�����:'��I�#��
���y���V�s���S(���r���M�������l�t�G����Wg�������#���r���a��O�������NG={p���%��&Mk��^-+��+uM�Z��!�_dmaA{�\U#��c@�
dj�:�Tw���<3nF����8�(!(j������2?���x�;K�aT�p.������&B���b``�/Y1�%�����Mo��N~��WW������'�m��'�e��Zf������z������\|�����B��Ia�t8<��w��,�mK�G&��O��?���|J���4�tiB�P��9�40��BS�1�dK���\�i�����<�{�������E��r��az,!���������������"�>�o�bC����lE6�s�"����S
f�2��S���WMG����h�c�%G��IM������P:���47P�Q���~E�yzJi3L�	�n�U�E��{7�f����*�	�����c��~�x�J�i��=�N������u���������V����V{HE0�9����G�pC7�&;9f��?��/��D�2(#���)�W�zU*�.���T�2�����v�������-��7�^4�m�������m���+��d;--�BNOv������������s������O�^��wV�y�3��RH�E�(_���X[� ,/R����7�Vk�h��^���
��YC�������$a�����2��u(����e���:,�b�#���:L%�(����tG��������I�N;��6�E��;Z�����l��w��PihX���
=LQ���z��K}��� ��7�2��]�K�=�����(���>�cY� �}}��~�4~��.2�X�kI�>�J�����_Ki.�r^>���~2n@��)��5��E(��):�O"�U�������|�o�*9aKM�w�<����qVK�m��!���-W��W�SM��P�� _�o.�n[	wMC����&�S�n���WS����Z�.�Rm��{���]����$�"i�~�\��T�2���Xya�u�����r�����_m��?����7�@�6�~�`s�3��dt���>k�������Rkwu����7�����[�n�����(2$k�����+�!4��Ic���8������)�bk}�M��o�������H
]��:C�A1�����)��|#�������Q
��kA�7���S��"7��
3�g�4l�x#�_Q��f#�i�F���e���d�<����lm����[Ow�����]��y��I�<l�U����=���N��G=��M�&������d�
.@9���7��AO�N����Mo�kR�^�]��������U�\q�%	������pN/$��aKo��k���I�n�4"!������z�]zs+d_��d�.��Iwq��
�/x����~K���A�*����\3�zN�`��0Y�jH�������G�~0���36�M������y�j-��9���#�R�a��~�[���)��j>1��DGQ�������x���������X
�e�$O5*(^�"���{����8`����[�Bz��h��7�p|y=���J�-�I	�����\��0gk�a)��J.����tb���N�����9F�]L*�F�2��cx��.zX#����/>_?:zw����e��p����� �%Q�C�[<��"��{������>���=�s-���t('���JZ����VV+�Eu*�7�6�8���Y:�ec[Ej��U./�b�N�-:J[]J\	"����/zY�8���M	�B���L�~]��U(��igJ1i[e�����5������E�c���B��Y_�������H���A�]�/^)>�qjl�=�6�}�e�n�	~�bF�0������a���#���s3d����}�0�v�~�y���H���h��VL3b���N<Q�<�������k�*~�J.E�"��)
��t��6]{y�������*�S�^����=m/�9�O[�=O�^bb�[RMi�	mK���p�Cjqa8���r���������6S|^������	��-s-��QC-��j���H������ut��%3�E��As��L�B����oy�hcS��5����{���C�b��V[1=5#�A8��#xt�4i��2����l��#���n4�1g���wV@k�"�)����� �K��t��]���
���2���J�Y��\�<��������`��"#�q���p��S!T��q�`��2�����~s<���R1yd����j�(�����bd�	��e�@OV�,��?���z
	Pezg$��r���7�v�����������mS��]zI\�L��U��O�P���:.m�*�1������1�#$C�r�2P*(�����?jA�/E������Z�d
�:�F���'��J6`�s����CY�Q{`4J�o�D"���Z�4M�e����oec�|uQ5�%��0
�:����N�q��%0F����<��Y��(��������X�#.7
>���]r�Rm%$�i����#����|�k�u��7��:�\4>TN�N<��A��uR�)y/���Mw1�y���������
|G�eOv��Y�t�������J����~)��t�uj$X�^9S���+�r�}��+��E"n�2��e�P.1����:h�7�&�1�$~����VN��}	�A��Q���y�������3��?�h2��&��������������d��$�fa�����m
�Al��z�I��I���u���z����\(g;f��1a99g��Q+�w�`\�E	��([�I#u/M��K�RTo�@K�w�P������������o����q�jlm!P�m
��H�zj�EN��C�`��C�o7���7�-�_1�<�T�����
�����v?e�\R�T��0`�*�f��I[`5dK����>7�`[���1�?V�e=�-���\�?��aO�f�.{�sH���N���W���i�?�B5����7�>�'ik.N����K�3I�i�9u�Ew]:�}{.u%����>
!M�#�/��t'��hW������4��`��C�?t�H��1�9�����J^�.��^����X$Y	�,G�2��+|������k����p:�I��1����0�xa�XE��Lz�6�D�|������DE".i>QnB5��hv�!��r}���Bx��H�q�y!8��`2fEz��*�J��B�8l*��T!��r�N����!G+%'y%�58���/�Lw��	�����J��~����������t���:�Ri�y���&����x������P$�E�Q8 E����zC���3I���Q�XsbUKr���&Mn0�z�(���u����:?z����	}�2���C�q����Y�W���\��4������q0)(��TS���RV��8������i06��$@uj��A�����l	�-�<��#t"�a�Uj�(��kH;��� �=�y]*xN>�m�Ue�i�A�*�����D���H?\����<H���p�Z��x2�T�I�y����:J�U�� T*u��9Z��rV�fe���)gz���b�'X0����Ua���$1Vm�r ����1'�HE�p��h���\��oP���>;�`jI��R{���]u�^>BO������LW��^X@�
q^�I��^g���������m����af_�L��9�^����KOY�"<�T��K�l��-�x��=@}.����)��9e����}bsqY)g{/�w�#�{0�G�W��h���d�����jf���V�������J��&�M�]�Q%�Wv����`E(>9	�������&P���B�4�a+�=1��K����5"^������t�+f����L`1������
�����]8-0�7qw?�5j>��cJ�S���N�@r&F�(\����]���W��7~<�<�o�65��"�������kz�9��9v�#;	�4&&+�g��\%j����c�f[�C���������\a�@�D��_+�1������:�cM���	+���.�+��^�
�9t<Kl?"cH��C��=��A�Q�j�yl�b���i��jb��`��wf���Jj���B6cS��5��"#drQ?nWj����d��"���#����������D����G!F����v�g��n�k�E[I%���_�$:�;��6g� ��x�Nz���3L�a� ��!�_����9������8�+0�;E�=d�K�i<����� �z�[D#!L�=�oZ���t
��E�G#�h0�c�N��,O�5����<���d7�����	���/�M���
;�s��X6��[��%��(/��"n#��Z��CQ�f�
;�6U���	o��X�.������g��Jd���&N�`�0������i���B'�Z5�����E$~��_�E�������*����;�g�E7��C����I"���SG�f����C=�;m�rIe:Y����������M����v�h*qO��9
����L>m�r��MX�����G0�C���6�[J�(3��Y��r�t�%��BY���0��C�:��n@bG'����t����Y&�vS�V	��0�g��:w�~�(2�����%b�Q�ji��<���l��+���fOs��"��>����^L��Q�F�Y�?�IS�M��N���kn��
d��1�;�y�F[�'
o,����+�������f��Z�*������c��@���\'�����,��V Cu���BYl�]����y���������&K��m:GD5e�d#����.��������������&�6�`��Q��'�1nC�_nc)KPK]��,A�[��#4��Y�*4{|eu��o(�����@����Jg�x�RS��m77�����wm�qZY��Y��������&I9�����ORwt����oG�";:�z4���v��%(�� 
�~���4P;��E������vB\3��c?�N��*�&���"Zv��T!/&f�y6��]HL�]K&M~�{��<acij-�4���"��h?�����(���<��]���C�&X��(S���@r����N��6{����t���vN��7�C��.9��o)�#F����������`$��m�r.���sjMI�dg�k�����G��*�r&31��������2aa���5���Yt��*��{h��S����.������Z�q����J����h4��/{NU�i�z�%Lw�D������}Y6�h�N	c�Z>]�l�<f�������4N<%+������tV�*�\B����sgl��lU������5^bo:�b��a0��k��0� ������@v�`����fZ�������8��|�^��L�e����z���vK����d�����3�Rb3	4��|��9������YG��67���
q����q�����=
��a2���ge`F0����u,$F�����Y�P'�����0F����}�l<���|�h��2�������	�pT}v�*��_� ?��PzF�����P;�E������V_�z���H9V��Z��[?MN���d�. #�S��������r��x0���wbe�@8��$
OpZ��-'^����&G{�?VN�(<cH���a3����_����)k����j2���w_d�<i��T=�������gW�w�+J�u�$�,�^��Adq���tijfE��QH�GF:���lNft��>X��I�Y���ks��NR%.��fT@NYr�����4���������M4z���~�=��!��J���j2��N���T����Phd�:���`2N�:�u���.%o����hCP�����E��7��h��+��SV���X
:)\�����ci��/qN����H��{2-�+�5T\�Q���G��YFS���)����#��5������� R�js��u�gVM���0*VF����n�8���eh��,���Df�hK���m��n�yR�����l���W�d��l<K	���������
�������d�vlO��-��}��5�g�_���B�y��,L�a�I��{����Re��5)$O���g�������i����~Ew�.�T���D��R���� I��u�����)v���"]U�8xZ�]�~�>��`t���=fN�3������IM�t*�g����-���i|� ��;�����
*�a�di�eVVd��JF"���k��2���_*+�]��(�IX:�����;�o��1�G�E;_���`��b�.����n��N1�����g&���i���%a�2���*8[
�57�r
6-�2�!�x����5�u��tB���S�A�:� ���6_������"�
�+c�������KGB%���C#	��U���y
�z���<��UQ�\��*��w9
a���\�k��8:M��+�ao�x�uW�P�� ��O'%�����"��99�}3l�G���	���bX������Va�=��v���	�1�����h,�{����B�-4���J��S:���%f=>7��Cy��Q�	�k���'������T��
gR����Q`�s�/;m��No��Y������~8���S���u_{)!N�p^�8�<{[�t��Y��MI+=`�����h�\�t�����
����v���Q�7,\����������7�S�k�t��b�5���T�-c��
�zvv�������U����Z�^7�������Q/�+�_���M
`��@��J�����O:>?���dJ`����:4$H�J�E��+��rU�~|{���p��������_>V9�5�#�L8mQ/hrz��&�tt�����i�������[�{��{B�C��������H����>�
���Vv���V���\4 Ty�,��B�)DI��#�$�����7#����a�����%����v�i����d��[����aeZ�9��������dhw�WrZ6FNJ[���{AO1f��>RM}�2�TX����2B���M�6e���K4��������p>����,9�������,�$y��tN��i��U�9YHB��!�8��f�Qj��9������������`�N��pN��T>=kA��]TW�������tcU��jr�����{&A �m���#����������m�S��L��������r��J�(�
�P������5���Q��5�3�Z���3�y
U��+��T��tl����h3��Ar���cu4���eG�&���:c����w�7A�������Y�U'�����<��V����.����3���.*�?7��W��Z��^���b���g���Zy����61
u5X��,0��d.
�?��6o"�B
��j�y,Yz|�O��k|�a��J�<y_=�t��P�7�0B��;m]�W�.��O��MR?2�X��k���vh�|E��m�n2j&�e*4�B)��:�+S^b,a����h��(*t����F?����2�����=sKS;��w�K�.W���W��'����+����������Gf�^�V���&`���z����J���`�i��������u��	5
��z�*��������������n�� N?��<�����.��q������5|��I�j�H�����N
@G�5������Q�X���}�����0`9*��Uk5 �1����=��_�Q�Kbzy��������O��Z[���$��'����7��W���Y��7w��Ri�ey�`�)���{{+����zX���������z�x(6�������W����N������d��}�o����-@�<���f2�t�-L��R���^��/���{�7�0lS�X���]��^q�p�����7�5Z����\[ M���-��W����$���N���/�_� F<j3��!B���-^Y�=���C8W���N�.��:�5�A�`�,;�;���o����)�L����D0G'���Z��Z�p'%���t����cAe���w�����������)�x��]vJ�7Vw�}/o��h���`N�����aT�a���A9�T&DP5+,WF��OE��&�l7b{`��e�h��Z�#��f)kC��)C'��������a�\�*Z*�����X1L����\���%����W��b��*e�������$����B)I�@��~MqC��2�/KA�K&3��1�E����&s^4���+����
��J�z���F���4&4��&�	�������Hg��G�tN(����G�G	��@����
�)�4�� ���SL^������b\�	a�mv��K��w�����p,8���D^:F��;�6yCl�;
J h���M�~�,�l�;C��?z��
m�K���Xjf�>������ ~���/�#��M���kL�FF��(��l�o���428_�����j����,K�]�+	7�bN��rgk
�4)��Yy\C���oB/!q��0;����3gC7�w�>���D�p2���8��F�7Z�?���DT�N�a��l���%�2L���s��A�.�J+i|�+D�0s��|��;|!��l����]��/������jF�w�6rv
�
�F�����1�M-bx�g2)����|@�D3���/|?'��0�7�*h��.1���n<�p���(gY�7b�Lri��%@���������
�#��q�T6�A�����x=*e�1����g�������L�|�n�IAX�)��V�w���7�b��Y�������A�d���F]kXj���M%�K�
1_��]��#~�X������F�Ypx&�}���������������!��N�N�tH�[� ��k+*�mh��9��=����_�9o�����ci>�}[��v��@O�E�����ni|�u�����
Z�C�Q���p��=p��O�y>.xK�;�]�������[��S�\@�P6��t�LD�T�Q���3\m%!{jz�@-R)�y��ES}Cj<�J`��H
C��1��`S�eM�lg��������'[�a@x��dm-�O~�O��l������/y<��i�dF�D�+J:3�I(����A�(��.~Jd��O�R~M�T'������Sb4�������	�=�1)~ld�<>h�{��R�Vw��3�S�-�\L=rf>n"�9�9�����A���j.������d��-����2��fd�Y|F=i��x�g�(����ri���	?��J;�x+�-*z�g"
�lv����P*m�'��i��[���-4����X.���"Y7Q��0
�n�Xxev����>�F@���T7���'$��d��Gqr]���Q?VN�%j����u��p4�i�tE�=��@��~����~W"
h��|����?x��;������k��:T�~�����(JtL�.?|�^���EZQqV�W�[D_�r�mv�����,hR��&Tj
�	("1�T�i�vv[�"f�S���>E96���2��Fv1~a�X�lLA�
9�,(����"�O��M��`�R��%
�@W���rZ]��z���%�+H�=���#�"u��oZ�fH�'����^5���)I�*�M��kw�P�3��D�Z�|��y����������J��E�CuJj�X�(�fK�.�>�g�{,-�j���kO�i��U;8^��B�}����af*��)S��y�z-`5�5mD���)������~�n1vZ��7���^bC��Y�����Gi���6{��g�����K�W~�3�Cn��1��x���h���{jd:
�7�;;�3���c�U�S!��R��r�D��aLE��j'��l�DQn�9�'�sh�_T�}Z��C���m_��*��(!�=�oUH����l��J{������>yn3�6�.�d�������	\AcuW�A��),�����8����
[;����
n�I7h��{U���n�����B!��l��}��6��w����5�ewp�yQ���s��`�vw��Vp��meq�T
L]�{���!��oJ�,�!w�Z9TP���;���Ms��cTo�O��7�'S��A��J\)��W��c���k�Q�N���IoaW�4I�[����
�5>kBy���F(����q����7>�5�:v����nm99�N�5��E�:H{�Ho�y<V�����5N����������g��@��QWzu��r��� Z�q}��M������T�D8���q���r�%�l���U�vc�6Z���#CXWxF�&\����u0l [L�H��k)�r�(;{���f)�-��������+`F
���
�m�N������8c���5YmK�����a�l���d*!7�u`��*�AV
��u<va�|'}R'���}	'��~����$���8Mpr>��M��C����������;J�.`;S~5�~��~��_U	x���dX
�i�2���@��v����VH�%�P���&������F����&�A^�h3���������W�����b��C{�jL2yN�S�1�0$a	�S�l�#J��^���oC����Z����Y���*��\�N>�����s:v���	�"��p"���g��j�<���B~�C1�pC�)S�l�[�5{�9n��S*����f;(�n���/_����-�qzY$
�|\��2���6l�U�`A���5
�*z|����q��3Q1��@�D�q�&f?�����$�(6[�s����Z�rZ$�&�,��S�:ch�-lDVF�X-]u[D-!
��	�v���l�q��ngPa!��?V�>$��z^�W5��en~��p���	0���3�p�.-���>.��^Y."9�2��f��s+�[VW;�np��4[�@�d��R<��U�S��3����Y
�9�G�)X�f�Tg�������T-�@�����4nuT�h�R
c���5�b�c���?oH����%;iw^�� ��jo��KL��e���z$����-�H+�P�����e,lH��[c�K4Hf��[4T����1�B�z�e����}�.#~�Q���%cOvcO�bO����������>��=Q���!�,	<�u�x��.	?�)G��`��}d�Po�w0�
D��B!*N����$HC�������x��{��@�v[���[n��N
D��9j�mP*��L�����j�	�!����u���������42>���|x��D	�������4���./g��9�M�F�}���zt�(^������~�6�����+���zl)`���w����/->��2���+���p�;���`��C@�}����HXY��^������vc��o����=���Y
Gq>\
��qry�*�#��ZMs8 ��$�60.���5�������51+�=M4~�����]|.r���#��
����,&r���U1����6���+6u�C�5
l��V`��������R+���|~�B�gt�&$����XV6F2����{e��C�+
�{���!YSh��F���]�'����=�U\�Y){cN^�T '�Ui�m�������@���#�{*�)+�X����L�&}�_�'y[���1�0��v�w���p�h4<��D����A�����y���.��N���1�5���������k����+3���>������G�G|�J/�����f��D{�g�(/��=��M��'@�;������G�k�`�g[��,��=�h*5s-Y<J�����$�'�	��
I��:�������e���t`�?p�2���u`�p���Ie9��)��,�9BF0�@n?��v1�:�H�Pj�hME��s(U�YF��fE3�&�
h���$��q�3�=.?!�Q�
j"�>��1!���0b������\q
7�@�g[�R����W�����yO����)}��3{B���;J��e�����	�@M�!n�R�J`7�J���u���o�#��o��bp������=����Y������Hz�(v��b�=��>����{���v�����^d�b��\���7ey3�s_ruR��_����6���F��/��_����p���9�~�����$�s�rgE���M������i���=C��
�!��P�e�/�����~����r
��/���\IP~(�O��@?����Gg����0<�����c���"�&gU�nb��X��-w0��'�/zx�xbh�4XR�9����T�^�W���
6���j�V9�������kqu���tR�"w���z���_���#���]����9��Y���%���G������..k������(�BB6� �l�Gw6z+C�JM������b�������������:2���><V2�rY��b^Kj�����!�e�0����.��L�1ng��:.{���������U��(Vh�"��t-���@��
@�c��F=�q��6E3c�&�f
���-0e������G{6e�-
������)�Q3�����6<��4�������ePi�|�]�������Lt�Vw����xZMrO�`d������������[��1��ELa�����I��4%rr?�<`4b����2�O�zSw0!�b�;C����I��a��������E,�>D��n�
��,����HKk���)���� �A9���,%����F,��hF@-��}����P���m��~�����z)���J�I�J�p1������o�����3�{��.���Yx���oY�%���iK�{�
��-�L��H�|P�����{��/��K��Z������
Cp�4�p&���@���$@@��4dqAuQ�<��Es'�7�}�8n�������M���X��3Ba:�5~��%���N��8�<1�m��1�����%���Qya����]��6Z����������(
F�$@�����o���`�N��������������M�4���,�)�y���`Kz|f?P�k�N�VDF����[�����~�<�0RI��#'|��?/M(��G���AO7'�8���Z�qR��W�YG�2��&>c4�� 0OD����Q(7�}Y,�"������|��+�AWO<6g����O����
X�y���Al�����2aJ�@#�)�f���0��dFx��bu�D���L���e��o�L�����c��'{��}�Oi���/1rh�p����O!U����)w��2B���9��{��<3��<E0����o>8'j+\,g� ���it�	���x�3>��,�����>����������yb/s�����������
]u��y&���u�����ML~������|Y�s����:��Ei���m��=���4�9|��7�m��y=������V_�7��P=��BT�WR�_���������;��2�o��d	6�+��8S|e�8T|M.���&S���_�g��Q
#48Petr Jelinek
pjmodos@pjmodos.net
In reply to: Pavel Stehule (#47)
1 attachment(s)
Re: poll: CHECK TRIGGER?

On 03/10/2012 04:36 PM, Pavel Stehule wrote:

and there some cleaned version

Reran my tests and adjusted docs a bit, tbh I considered the previous
versions way more useful but even in this form it's nice and useful
functionality.

Regards
Petr Jelinek

Attachments:

plpgsql_check_function2.diff.gzapplication/gzip; name=plpgsql_check_function2.diff.gzDownload
�	]Oplpgsql_check_function2.diff�\�s�8��,��N"Y��#N�����r�]����5;�E���g���ov����8R���������%@�����
(~0��v{d���co[��o:��0����~a��mA����{��y���y��;�=�c;�^��o���u�7���Z��k���;h�&���3x��������N?����~��+I�������;<����Y�r�D���+����	�r�[g��F��1wo���]��e1K���������M����8v����{������Hp/�e���Xl{3�����M��(���t�
��'�������n��8����o1�8�3�[sX�`A$p�Gn�����$'������37��
�3������)��?��n��i����	����n��z�8��O������G����F��j���0�V��"��F�fAzl9�����ze������Z>��e��~���e���y�w�w8��>
��8���o2?�b�����}���Cv���uG�����I�Y���g<�l����6�?��^�SY.��{,����J���G���'v�~�!�� �������������i�t�[a���]	sbL��^��|�����*w����������~��f�sPv'��K������w�7�I�k�����X�����9(�N��f�z��/^<O�'��{������D�b����)�
\R�����n�t^u�C� ���u��7�������B��47�O;������k����4�����o��Ng����������-+������M���5�{���/�����}�GW�F�8�q|�'u�'l�����E��ScCS���-�G��Y}q:����j�zd���Lt�"�k&
m��O
��|$���������*<&H��VCC�(�P�s���SLp��&&�t����{�f_Y'|H"�I������0:��8�Vr�(�Ma5K0��i�@��@d�5
�	����	yZ?�`XQ�htT���6�W%bI�	�z�k�1����'�OX���4���-s�;,(��"v�),�"��4��)�hw�e����4����;I>��4������!����9|�`��=�hK�Y�y!��s�+�1U(`p�.*^h��sa� 2Z����j�z�H�Y�@��RZ�Y��9�� �89i��\�hLR�jJSa����� ��a�|q�����S�Jd���[��va�]�-���[x�c�@�S���;�����Gy!�|N���5A<D+�<���2���-{�I����z�0��<�2J�lL�I�����,�u�l�e�U�K��'�
����lVa��xg���`�x�����E`]�Q]4xK�`�5���_��s�����q!�A�"���lsYc�P��f��,���a<��p�����Ms�t�V�y�_��J+�����b[<����& ~�n�$�)��
���7U�� ��������c������m�8)�8�C�d�>�f��0j�=(�a[��Mde����lK�i�������v�{3:'4��bh�^��8$���o*N�1�+�A�v���g������e���l�XO�O8�����'����!��(�i������>�a
�( ��uo5`�'h��cjN���
Yu�<s��X��W*��LL"����o�T�4��D �q�[<Sc �0������	���N����A��Q�|GE6��ZRU��jOR
�()U	�J����%�T5+���FJb��������>���l�������Rz�E�;W������I�&^o�N/hhK>B�S�B_�a$�	zQ�$!e�w�f����`:��*^
���'��p��%�#g�Q~�'
���������G�Z�*�60����X����3�l��;P?�OS���IH[���-�rP��;���1d�=����[��|�3R��*)�:
�n%vc���'��<u�C���F9��J�)u��%�����!��A�V��/�?$p
�q��=�i�!^�=����bH�������C��. [S��,���]�����hJ�`�T����Tdr�#�"p��n�,"�������	��z��c����;��7ov�1�=�K#&��{L��
(����y��=����'�.�Y��R�bH�P��[~��9�t��WN��|���S_NR*	�u>/ z��Y�N�n����W
��t��Ag�?���tV�!;X��C=p����)J=�.��1i&��9���R{��/�^� �P?����c#��+x.>^]�[-���H������/�u���|�����
c�%�M�CW K�WJm�����q�+S��!i�
���L��]�44Ttk�y�i1����j�����0��i�[��wq�e������9/OG���i����?�E4	t����W�	z&�-�E�)��1�`D]���>]\���}���q���h�K����{��`(cZt
�ug=��w:�4�9W��s���z0���3V��bZ��0��s���v�MN)��w�%��B��Z��x��hH��e|�8n���9�	�����/�KP��a�/���0/(�9�����)� 7��,���X�l��(����UY����7�M�����b��S��`�:�������9���1DF��D_��CuCX-��Z9��@��Y�(},��6�l��m�ZE$V�W�g�A�X3z�<���a�;����� �m�!��%!u.�1L�5��^��s���d��\a�0�d�7���(���+��1]:b4^�'F#���q�Le\5��`\�(g��A��`��7 H�v���kc�5�
�gf	V����p�5�&VPa���`'n��l�F.�j�^����)`�z����O�p����^wD�WV?k���C�����8�J@;)81�G[�"�}����)|+��
|2�i`M�d!�rG�@���Zr��g��*��2�t7�J����$
����4aNNf}?6|�OJ�P�w��ri�`*����A���X����/��y�C���PZ[�st/����4St��9�e��9g<��8B�E+C=��\E#"�c4��N�pH:�E�@
Q�$�T����R�����N7J�U������[L�����h�������1��V�t��X�
i�����X��f��MM��b�T����)���[���e������D��(�O���U��O�t(2X9��?��B��d@
/J:t��B$����Z��K��!{k��q������~G�����Z���"Z���������-��3����-��?�)O��5[B���f� ����+�ud�4�
~r���~uL`[�,*��Ol�W��������6Y��,&��?|<�M�(�rD;�_�o>�hr�3��P�ui�g����=�'�Mc��zT��~����R5<��/�z+p���"��V q�I���3�����%�u��
�t���nO1���KAM�nx�i&5�iNB��������3���cU�����d�f��d�`1.����ge���Q)Iq)@l���}����$���"���,��/���x:w;�';��/T��I?%7&�k�U��.C�K�o��jWR�,�zD{�(K
�����������
�>L����,`GJ��J�$�����$wPi�����O���1��doV�
�7d��8���*��Ns_"���:�vp�/q{���\��z9��H�A�)J��� �*l
J?X}���_�#���X�H���ta�l��u���tx(������C���g< |z��YF�y�v��7�g�����y��c�qHy�-*������w�>��`��R0�#<�F��}F��h��|�R-h��w�_,5����RF�8�I�l�5���j��:�2���O�������9DNE�d]d���<�Sy��W:��A���,t�)�v<��AF�{p}:�P^c�3�j�H�b��#�����$�_�@�4}�S=]�x����q����!c���3%��s���g���z?*P)��<�����q�
�������G_�f6S���5��3����kIs�A�Q�D�����3,�
�*�=f�M�'<�kZt�K��]Q}U�*.�S����P�s��jh�@W�:�sh���9���U#oP)��3�0��nb�0���A�/�W7����^g������z��l�<f@�3Ru\��n��X<��0�K1�����2�R�Bcl���r�a�C���]S���r2�cY�Z�����}��c=**����k�X�0?�q3m9�{<w]0��.x�r]v��Zz���}�uy�0��V-�h���\1���\byJ�6��x����'�7��'�����=�? 9N@ ���8���
Al]��/r���<
&,��@O����g;�rr�]*�d)�=V>�����p ���� ���T���9\�;*��l��K@�}THJ&�������������"3��������=~r�c�'�(�!����K���V���:6Q9�$eeh�!�����W���Zt-
����u����pMsA[UB&Y-��jn����39�J��pB�H�U~*��g{�0�F���N�����ae�Z�'=�?<LD�L1IMV����2���<A"���O���2W�AH������b�b`/5��0!�<��/�������PH�T�k+���<x�<Q�&�FX�d�����kn�����B��p�?7���4��J���\��p�>g!F"��/[+���7 �����0�����������ao���S��?�97��Q�\w�'EE	�~�I����W����J]���l��Y[X�<�D�������Z��� ��4�%�����(�4i0�3J��3`��8����t&�CA��7A$����1�!�����,��.�KV�z���0#z�[���A>��Uq��.*��g���Iz�*���gk�����y��w�t�.������}RX8O�]�.K~��������6y��t2�8�R�h+�*]�P/ctA� 
�z���TzL7�Rs�!Wa�wba0��|F�;�|bq��\)��K�D��/;����+;<�����O������30*[�����})���Y�L��Tu����E�a��+M.G�-y�BSc���h(���9��
�����_�b��R�S�|q��jr�g�����#�s��t���������s��9^�y��xO��������j����t�.d�U|�b��^AR�dN��?$������M��N����Og�K��a���iA��e
���^�
��z�)��E��u������f`sr����
��}A[6w?s���~v(��~4�NKK����]`��}��p�axz�\�/�#��S��;��Uf^�����k�'��r���5��;���j�s)�
��<�%G���f/u�5s�P|0�8&�'�G���-����@�v��yA�pY�6��K(�����)��SG�x���� ��5�x/�t�y�����Mc����h��5:�0��>1TZ�s�BS�+��d�R*��.��������h����aE�4r�(Jj���X.�`A_���_,�_���=��Z��&��(�����R����W�;�������s
�`�rc���G�N��HkU�`���~ ����JN�RS�,����p�q��Rp[4rH��v��U��U�T���T��/H����K��V��F����>������:����ej������T�������hWu-C��#I�H��+�F:����(2V^��=�t�����~+kQt���oP���u�*����/�j���Y��q b�!����00H�I�$�#��Y���`�d��w�Z��[�l�%�H����u�����qP��#�#Sv��0����7��Y��%���?u���be�3TS���F:1`�_�U%�����
oGo�kEn��fx��i���F"���(�Q�F�����EW����^y�?y�=��dd�7+�����_?@�&'��5�Ny�(�@Gq
�{��%.��t��zP%�>M�+#+p���\>�r��?�o`���^����	�����J�:���_�G9-#����K�7�;�
��^H�
Z;���4�������iDB��m���7�����V�����0]R�9���&�,_�xe'��<S)��5T&;&���f����� �a�����	n��K���`�)d3~gl��VO�����Z�%s�G�8�4�f�6��S�v�|b$�������k�g��lq���1�*���HI�j$TP�pE"o���5q��cO�
�����B9ob!���z�!��~[2��01?2
F��o	`����R���\R������?�!�����s�.��>T*�XdP7"���]��F&�/._|�:~t���cI�����#Q3eA<K���d�xFuD����;��5�}D
]{��Z�sm�PNv�[��P)?����V��:�T�o�m�qry��t�������?�\^�����[t�����D�a+_���q�V�8���o��b��rQ�P�����b������=t'kjE��u�������4Y���/P]s����"�	�f��_�R|*���{dm>���~�.��-��za��es���	<G�'�f�����a������S�$/�������f��*�x�.�y:�{��=M��U���\�ZEr�S*,�� 1m��0�z�M�)��UT���|356{�^�r$$���{���4�������>����{%�>����p�
"���7	9O�*(Um��$���U}6U[�Z&5��Z���~y���������CKf��`Q��K#���,US��l���P=k z����L��&�����bzjF0�p��G��%h��ke�!=

�8aGbq;(�h�c�Z�-�����E�%R�%���!A ��=�������I7�e��U�����;x`�SU����MEF���a��L�B��S��3���eD���7��x�c40��b���=`��BQ��S�a��X������fYj���9�����HFC��1o�"i�'�;����VW��4��@�(8=��Y��� Ayu\�dUzc*�k�AczGH��3��e�TP*�w�'��_������'���uv��3)O,�l�*�8d�1��l���h���H�D|?*���Li��"����l���j$K�a�du��'U�|���K`�,�Ey�Q�T#Q����M�DG>\n|\������JH�*mCGr��*������o zu^�h|����x"^)�H[��pS�^�'y��bv�F7�����
�K���l��.��e�Y�t�������J����~)��t�uj$X�^9S���+�r�}��+��E"n�2��e�P.1����:h�7�&�1�$~����VN��}	�A��Q���y�������3��?�h2��&��������������d��$�fa�����m
�Al��z�I��I���u���z����\(g;f��1a99g��Q+�w�`\�E	��([�I#u/M��K�RTo�@K�w�P������������o����q�jlm!P�m
��H�zj�EN��C�`��C�o7���7�-�_1�<�T�����
�����v?e�\R�T��0`�*�f��I[`5dK����>7�`[���1�?V�e=�-���\�?��aO�f�.{�sH���N���W���i�?�B5����7�>�'ik.N����K�3I�i�9u�Ew]:�}{.u%����>
!M�#�/��t'��hW������4��`��C�?t�H��1�9�����J^�.��^����X$Y	�,G�2��+|������k����p:�I��1����0�xa�XE��Lz�6�D�|������DE".i>QnB5��hv�!��r}���Bx��H�q�y!8��`2fEz��*�J��B�8l*��T!��r�N����!G+%'y%�58���/�Lw��	�����J��~����������t���:�Ri�y���&����x������P$�E�Q8 E����zC���3I���Q�XsbUKr���&Mn0�z�(���u����:?z����	}�2���C�q����Y�W���\��4������q0)(��TS���RV��8������i06��$@uj��A�����l	�-�<��#t"�a�Uj�(��kH;��� �=�y]*xN>�m�Ue�i�A�*�����D���H?\����<H���p�Z��x2�T�I�y����:J�U�� T*u��9Z��rV�fe���)gz���b�'X0����Ua���$1Vm�r ����1'�HE�p��h���\��oP���>;�`jI��R{���]u�^>BO������LW��^X@�
q^�I��^g���������m����af_�L��9�^����KOY�"<�T��K�l��-�x��=@}.����)��9e����}bsqY)g{/�w�#�{0�G�W��h���d�����jf���V�������J��&�M�]�Q%�Wv����`E(>9	�������&P���B�4�a+�=1��K����5"^������t�+f����L`1������
�����]8-0�7qw?�5j>��cJ�S���N�@r&F�(\����]���W��7~<�<�o�65��"�������kz�9��9v�#;	�4&&+�g��\%j����c�f[�C���������\a�@�D��_+�1������:�cM���	+���.�+��^�
�9t<Kl?"cH��C��=��A�Q�j�yl�b���i��jb��`��wf���Jj���B6cS��5��"#drQ?nWj����d��"���#����������D����G!F����v�g��n�k�E[I%���_�$:�;��6g� ��x�Nz���3L�a� ��!�_����9������8�+0�;E�=d�K�i<����� �z�[D#!L�=�oZ���t
��E�G#�h0�c�N��,O�5����<���d7�����	���/�M���
;�s��X6��[��%��(/��"n#��Z��CQ�f�
;�6U���	o��X�.������g��Jd���&N�`�0������i���B'�Z5�����E$~��_�E�������*����;�g�E7��C����I"���SG�f����C=�;m�rIe:Y����������M����v�h*qO��9
����L>m�r��MX�����G0�C���6�[J�(3��Y��r�t�%��BY���0��C�:��n@bG'����t����Y&�vS�V	��0�g��:w�~�(2�����%b�Q�ji��<���l��+���fOs��"��>����^L��Q�F�Y�?�IS�M��N���kn��
d��1�;�y�F[�'
o,����+�������f��Z�*������c��@���\'�����,��V Cu���BYl�]����y���������&K��m:GD5e�d#����.��������������&�6�`��Q��'�1nC�_nc)KPK]��,A�[��#4��Y�*4{|eu��o(�����@����Jg�x�RS��m77�����wm�qZY��Y��������&I9�����ORwt����oG�";:�z4���v��%(�� 
�~���4P;��E������vB\3��c?�N��*�&���"Zv��T!/&f�y6��]HL�]K&M~�{��<acij-�4���"��h?�����(���<��]���C�&X��(S���@r����N��6{����t���vN��7�C��.9��o)�#F����������`$��m�r.���sjMI�dg�k�����G��*�r&31��������2aa���5���Yt��*��{h��S����.������Z�q����J����h4��/{NU�i�z�%Lw�D������}Y6�h�N	c�Z>]�l�<f�������4N<%+������tV�*�\B����sgl��lU������5^bo:�b��a0��k��0� ������@v�`����fZ�������8��|�^��L�e����z���vK����d�����3�Rb3	4��|��9������YG��67���
q����q�����=
��a2���ge`F0����u,$F�����Y�P'�����0F����}�l<���|�h��2�������	�pT}v�*��_� ?��PzF�����P;�E������V_�z���H9V��Z��[?MN���d�. #�S��������r��x0���wbe�@8��$
OpZ��-'^����&G{�?VN�(<cH���a3����_����)k����j2���w_d�<i��T=�������gW�w�+J�u�$�,�^��Adq���tijfE��QH�GF:���lNft��>X��I�Y���ks��NR%.��fT@NYr�����4���������M4z���~�=��!��J���j2��N���T����Phd�:���`2N�:�u���.%o����hCP�����E��7��h��+��SV���X
:)\�����ci��/qN����H��{2-�+�5T\�Q���G��YFS���)����#��5������� R�js��u�gVM���0*VF����n�8���eh��,���Df�hK���m��n�yR�����l���W�d��l<K	���������
�������d�vlO��-��}��5�g�_���B�y��,L�a�I��{����Re��5)$O���g�������i����~Ew�.�T���D��R���� I��u�����)v���"]U�8xZ�]�~�>��`t���=fN�3������IM�t*�g����-���i|� ��;�����
*�a�di�eVVd��JF"���k��2���_*+�]��(�IX:�����;�o��1�G�E;_���`��b�.����n��N1�����g&���i���%a�2���*8[
�57�r
6-�2�!�x����5�u��tB���S�A�:� ���6_������"�
�+c�������KGB%���C#	��U���y
�z���<��UQ�\��*��w9
a���\�k��8:M��+�ao�x�uW�P�� ��O'%�����"��99�}3l�G���	���bX������Va�=��v���	�1�����h,�{����B�-4���J��S:���%f=>7��Cy��Q�	�k���'������T��
gR����Q`�s�/;m��No��Y������~8���S���u_{)!N�p^�8�<{[�t��Y��MI+=`�����h�\�t�����
����v���Q�7,\����������7�S�k�t��b�5���T�-c��
�zvv�������U����Z�^7�������Q/�+�_���M
`��@��J�����O:>?���dJ`����:4$H�J�E��+��rU�~|{���p��������_>V9�5�#�L8mQ/hrz��&�tt�����i�������[�{��{B�C��������H����>�
���Vv���V���\4 Ty�,��B�)DI��#�$�����7#����a�����%����v�i����d��[����aeZ�9��������dhw�WrZ6FNJ[���{AO1f��>RM}�2�TX����2B���M�6e���K4��������p>����,9�������,�$y��tN��i��U�9YHB��!�8��f�Qj��9������������`�N��pN��T>=kA��]TW�������tcU��jr�����{&A �m���#����������m�S��L��������r��J�(�
�P������5���Q��5�3�Z���3�y
U��+��T��tl����h3��Ar���cu4���eG�&���:c����w�7A�������Y�U'�����<��V����.����3���.*�?7��W��Z��^���b���g���Zy����61
u5X��,0��d.
�?��6o"�B
��j�y,Yz|�O��k|�a��J�<y_=�t��P�7�0B��;m]�W�.��O��MR?2�X��k���vh�|E��m�n2j&�e*4�B)��:�+S^b,a����h��(*t����F?����2�����=sKS;��w�K�.W���W��'����+����������Gf�^�V���&`���z����J���`�i��������u��	5
��z�*��������������n�� N?��<�����.��q������5|��I�j�H�����N
@G�5������Q�X���}�����0`9*��Uk5 �1����=��_�Q�Kbzy��������O��Z[���$��'����7��W���Y��7w��Ri�ey�`�)���{{+����zX���������z�x(6�������W����N������d��}�o����-@�<���f2�t�-L��R���^��/���{�7�0lS�X���]��^q�p�����7�5Z����\[ M���-��W����$���N���/�_� F<j3��!B���-^Y�=���C8W���N�.��:�5�A�`�,;�;���o����)�L����D0G'���Z��Z�p'%���t����cAe���w�����������)�x��]vJ�7Vw�}/o��h���`N�����aT�a���A9�T&DP5+,WF��OE��&�l7b{`��e�h��Z�#��f)kC��)C'��������a�\�*Z*�����X1L����\���%����W��b��*e�������$����B)I�@��~MqC��2�/KA�K&3��1�E����&s^4���+����
��J�z���F���4&4��&�	�������Hg��G�tN(����G�G	��@����
�)�4�� ���SL^������b\�	a�mv��K��w�����p,8���D^:F��;�6yCl�;
J h���M�~�,�l�;C��?z��
m�K���Xjf�>������ ~���/�#��M���kL�FF��(��l�o���428_�����j����,K�]�+	7�bN��rgk
�4)��Yy\C���oB/!q��0;����3gC7�w�>���D�p2���8��F�7Z�?���DT�N�a��l���%�2L���s��A�.�J+i|�+D�0s��|��;|!��l����]��/������jF�w�6rv
�
�F�����1�M-bx�g2)����|@�D3���/|?'��0�7�*h��.1���n<�p���(gY�7b�Lri��%@���������
�#��q�T6�A�����x=*e�1����g�������L�|�n�IAX�)��V�w���7�b��Y�������A�d���F]kXj���M%�K�
1_��]��#~�X������F�Ypx&�}���������������!��N�N�tH�[� ��k+*�mh��9��=����_�9o�����ci>�}[��v��@O�E�����ni|�u�����
Z�C�Q���p��=p��O�y>.xK�;�]�������[��S�\@�P6��t�LD�T�Q���3\m%!{jz�@-R)�y��ES}Cj<�J`��H
C��1��`S�eM�lg��������'[�a@x��dm-�O~�O��l������/y<��i�dF�D�+J:3�I(����A�(��.~Jd��O�R~M�T'������Sb4�������	�=�1)~ld�<>h�{��R�Vw��3�S�-�\L=rf>n"�9�9�����A���j.������d��-����2��fd�Y|F=i��x�g�(����ri���	?��J;�x+�-*z�g"
�lv����P*m�'��i��[���-4����X.���"Y7Q��0
�n�Xxev����>�F@���T7���'$��d��Gqr]���Q?VN�%j����u��p4�i�tE�=��@��~����~W"
h��|����?x��;������k��:T�~�����(JtL�.?|�^���EZQqV�W�[D_�r�mv�����,hR��&Tj
�	("1�T�i�vv[�"f�S���>E96���2��Fv1~a�X�lLA�
9�,(����"�O��M��`�R��%
�@W���rZ]��z���%�+H�=���#�"u��oZ�fH�'����^5���)I�*�M��kw�P�3��D�Z�|��y����������J��E�CuJj�X�(�fK�.�>�g�{,-�j���kO�i��U;8^��B�}����af*��)S��y�z-`5�5mD���)������~�n1vZ��7���^bC��Y�����Gi���6{��g�����K�W~�3�Cn��1��x���h���{jd:
�7�;;�3���c�U�S!��R��r�D��aLE��j'��l�DQn�9�'�sh�_T�}Z��C���m_��*��(!�=�oUH����l��J{������>yn3�6�.�d�������	\AcuW�A��),�����8����
[;����
n�I7h��{U���n�����B!��l��}��6��w����5�ewp�yQ���s��`�vw��Vp��meq�T
L]�{���!��oJ�,�!w�Z9TP���;���Ms��cTo�O��7�'S��A��J\)��W��c���k�Q�N���IoaW�4I�[����
�5>kBy���F(����q����7>�5�:v����nm99�N�5��E�:H{�Ho�y<V�����5N����������g��@��QWzu��r��� Z�q}��M������T�D8���q���r�%�l���U�vc�6Z���#CXWxF�&\����u0l [L�H��k)�r�(;{���f)�-��������+`F
���
�m�N������8c���5YmK�����a�l���d*!7�u`��*�AV
��u<va�|'}R'���}	'��~����$���8Mpr>��M��C����������;J�.`;S~5�~��~��_U	x���dX
�i�2���@��v����VH�%�P���&������F����&�A^�h3���������W�����b��C{�jL2yN�S�1�0$a	�S�l�#J��^���oC����Z����Y���*��\�N>�����s:v���	�"��p"���g��j�<���B~�C1�pC�)S�l�[�5{�9n��S*����f;(�n���/_����-�qzY$
�|\��2���6l�U�`A���5
�*z|����q��3Q1��@�D�q�&f?�����$�(6[�s����Z�rZ$�&�,��S�:ch�-lDVF�X-]u[D-!
��	�v���l�q��ngPa!��?V�>$��z^�W5��en~��p���	0���3�p�.-���>.��^Y."9�2��f��s+�[VW;�np��4[�@�d��R<��U�S��3����Y
�9�G�)X�f�Tg�������T-�@�����4nuT�h�R
c���5�b�c���?oH����%;iw^�� ��jo��KL��e���z$����-�H+�P�����e,lH��[c�K4Hf��[4T����1�B�z�e����}�.#~�Q���%cOvcO�bO����������>��=Q���!�,	<�u�x��.	?�)G��`��}d�Po�w0�
D��B!*N����$HC�������x��{��@�v[���[n��N
D��9j�mP*��L�����j�	�!����u���������42>���|x��D	�������4���./g��9�M�F�}���zt�(^������~�6�����+���zl)`���w����/->��2���+���p�;���`��C@�}����HXY��^������vc��o����=���Y
Gq>\
��qry�*�#��ZMs8 ��$�60.���5�������51+�=M4~�����]|.r���#��
����,&r���U1����6���+6u�C�5
l��V`��������R+���|~�B�gt�&$����XV6F2����{e��C�+
�{���!YSh��F���]�'����=�U\�Y){cN^�T '�Ui�m�������@���#�{*�)+�X����L�&}�_�'y[���1�0��v�w���p�h4<��D����A�����y���.��N���1�5���������k����+3���>������G�G|�J/�����f��D{�g�(/��=��M��'@�;������G�k�`�g[��,��=�h*5s-Y<J�����$�'�	��
I��:�������e���t`�?p�2���u`�p���Ie9��)��,�9BF0�@n?��v1�:�H�Pj�hME��s(U�YF��fE3�&�
h���$��q�3�=.?!�Q�
j"�>��1!���0b������\q
7�@�g[�R����W�����yO����)}��3{B���;J��e�����	�@M�!n�R�J`7�J���u���o�#��o��bp������=����Y������Hz�(v��b�=��>����{���v�����^d�b��\���7ey3�s_ruR��_����6���F��/��_����p���9�~�����$�s�rgE���M������i���=C��
�!��P�e�/�����~����r
��/���\IP~(�O��@?����Gg����0<�����c���"�&gU�nb��X��-w0��'�/zx�xbh�4XR�9����T�^�W���
6���j�V9�������kqu���tR�"w���z���_���#���]����9��Y���%���G������..k������(�BB6� �l�Gw6z+C�JM������b�������������:2���><V2�rY��b^Kj�����!�e�0����.��L�1ng��:.{���������U��(Vh�"��t-���@��
@�c��F=�q��6E3c�&�f
���-0e������G{6e�-
������)�Q3�����6<��4�������ePi�|�]�������Lt�Vw����xZMrO�`d������������[��1��ELa�����I��4%rr?�<`4b����2�O�zSw0!�b�;C����I��a��������E,�>D��n�
��,����HKk���)���� �A9���,%����F,��hF@-��}����P���m��~�����z)���J�I�J�p1������o�����3�{��.���Yx���oY�%���iK�{�
��-�L��H�|P�����{��/��K��Z������
Cp�4�p&���@���$@@��4dqAuQ�<��Es'�7�}�8n�������M���X��3Ba:�5~��%���N��8�<1�m��1�����%���Qya����]��6Z����������(
F�$@�����o���`�N��������������M�4���,�)�y���`Kz|f?P�k�N�VDF����[�����~�<�0RI��#'|��?/M(��G���AO7'�8���Z�qR��W�YG�2��&>c4�� 0OD����Q(7�}Y,�"������|��+�AWO<6g����O����
X�y���Al�����2aJ�@#�)�f���0��dFx��bu�D���L���e��o�L�����c��'{��}�Oi���/1rh�p����O!U����)w��2B���9��{��<3��<E0����o>8'j+\,g� ���it�	���x�3>��,�����>����������yb/s�����������
]u��y&���u�����ML~������|Y�s����:��Ei���m��=���4�9|��7�m��y=������V_�7��P=��BT�WR�_���������;��2�o��d	6�+��8S|e�8T|M.���&S���_�����P
#49Pavel Stehule
pavel.stehule@gmail.com
In reply to: Petr Jelinek (#48)
Re: poll: CHECK TRIGGER?

2012/3/11 Petr Jelinek <pjmodos@pjmodos.net>:

On 03/10/2012 04:36 PM, Pavel Stehule wrote:

and there some cleaned version

Reran my tests and adjusted docs a bit, tbh I considered the previous
versions way more useful but even in this form it's nice and useful
functionality.

thank you - this version is base, and it doesn't block any enhancement
in future.

Regards

Pavel

Show quoted text

Regards
Petr Jelinek

#50Pavel Stehule
pavel.stehule@gmail.com
In reply to: Petr Jelinek (#48)
1 attachment(s)
Re: poll: CHECK TRIGGER?

2012/3/11 Petr Jelinek <pjmodos@pjmodos.net>:

On 03/10/2012 04:36 PM, Pavel Stehule wrote:

and there some cleaned version

Reran my tests and adjusted docs a bit, tbh I considered the previous
versions way more useful but even in this form it's nice and useful
functionality.

remove two lines of death code

Regards

Pavel

Show quoted text

Regards
Petr Jelinek

Attachments:

plpgsql_check_function2-2012-03-12.diff.gzapplication/x-gzip; name=plpgsql_check_function2-2012-03-12.diff.gzDownload
#51Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Pavel Stehule (#50)
Re: poll: CHECK TRIGGER?

Ok, seems that the API issue is settled, so I'm now looking at the code
actually doing the checking. My first impression is that this is a lot
of code. Can we simplify it?

Since this is deeply integrated into the PL/pgSQL interpreter, I was
expecting that this would run through the normal interpreter, in a
special mode that skips all the actual execution of queries, and
shortcuts all loops and other control statements so that all code is
executed only once. That would mean sprinkling some "if (check_only)"
code into the normal exec_* functions. I'm not sure how invasive that
would be, but it's worth considering. I think you would be able to more
easily catch more errors that way, and the check code would stay better
in sync with the execution code.

Another thought is that check_stmt() and all its subroutines are very
similar to the plpgsql_dumptree() code. Would it make sense to merge
those? You could have an output mode, in addition to the xml and
plain-text formats, that would just dump the whole tree like
plpgsql_dumptree() does.

In prepare_expr(), you use a subtransaction to catch any ERRORs that
happen during parsing the expression. That's a good idea, and I think
many of the check_* functions could be greatly simplified by adopting a
similar approach. Just ereport() any errors you find, and catch them at
the appropriate level, appending the error to the output string. Your
current approach of returning true/false depending on whether there was
any errors seems tedious.

If you create a function with an invalid body (ie. set
check_function_bodies=off; create function ... $$ bogus $$;) ,
plpgsql_check_function() still throws an error. It's understandable that
it cannot do in-depth analysis if the function cannot be parsed, but I
would expect the syntax error to be returned as a return value like
other errors that it complains about, not thrown as a hard ERROR. That
would make it more useful to bulk-check all functions in a database with
something like "select plpgsql_check_function(oid) from pg_class". As it
is, the checking stops at the first invalid function with an error.

PS. I think plpgsql_check_function() belongs in pl_handler.c

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#52Pavel Stehule
pavel.stehule@gmail.com
In reply to: Heikki Linnakangas (#51)
Re: poll: CHECK TRIGGER?

Hello

2012/3/28 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

Ok, seems that the API issue is settled, so I'm now looking at the code
actually doing the checking. My first impression is that this is a lot of
code. Can we simplify it?

I am afraid so there are not a big space for simplification :(

Since this is deeply integrated into the PL/pgSQL interpreter, I was
expecting that this would run through the normal interpreter, in a special
mode that skips all the actual execution of queries, and shortcuts all loops
and other control statements so that all code is executed only once. That
would mean sprinkling some "if (check_only)" code into the normal exec_*
functions. I'm not sure how invasive that would be, but it's worth
considering. I think you would be able to more easily catch more errors that
way, and the check code would stay better in sync with the execution code.

This can mess current code - it can works, but some important
fragments can be less readable after. Almost all "eval" routines
should supports fake mode, and it can be little bit slower and less
readable.

Another thought is that check_stmt() and all its subroutines are very
similar to the plpgsql_dumptree() code. Would it make sense to merge those?
You could have an output mode, in addition to the xml and plain-text
formats, that would just dump the whole tree like plpgsql_dumptree() does.

yes, it is possible - first implementation was via walker, and it was
reused for dump. Current code is more readable, but there is not
reuse. I am able to redesign code to this direction.

In prepare_expr(), you use a subtransaction to catch any ERRORs that happen
during parsing the expression. That's a good idea, and I think many of the
check_* functions could be greatly simplified by adopting a similar
approach. Just ereport() any errors you find, and catch them at the
appropriate level, appending the error to the output string. Your current
approach of returning true/false depending on whether there was any errors
seems tedious.

This is not possible, when we would to enable "fatal_errors = false"
checking. I can do subtransaction in prepare_expr, because it is the
most deep level, but I cannot to use it elsewhere, because I cannot
handle exception and continue with other parts of statement.

If you create a function with an invalid body (ie. set
check_function_bodies=off; create function ... $$ bogus $$;) ,
plpgsql_check_function() still throws an error. It's understandable that it
cannot do in-depth analysis if the function cannot be parsed, but I would
expect the syntax error to be returned as a return value like other errors
that it complains about, not thrown as a hard ERROR. That would make it more
useful to bulk-check all functions in a database with something like "select
plpgsql_check_function(oid) from pg_class". As it is, the checking stops at
the first invalid function with an error.

it is good idea

PS. I think plpgsql_check_function() belongs in pl_handler.c

can be - why not.

Regards

Pavel

Show quoted text

--
 Heikki Linnakangas
 EnterpriseDB   http://www.enterprisedb.com

#53Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Pavel Stehule (#52)
Re: poll: CHECK TRIGGER?

On 28.03.2012 23:54, Pavel Stehule wrote:

2012/3/28 Heikki Linnakangas<heikki.linnakangas@enterprisedb.com>:

In prepare_expr(), you use a subtransaction to catch any ERRORs that happen
during parsing the expression. That's a good idea, and I think many of the
check_* functions could be greatly simplified by adopting a similar
approach. Just ereport() any errors you find, and catch them at the
appropriate level, appending the error to the output string. Your current
approach of returning true/false depending on whether there was any errors
seems tedious.

This is not possible, when we would to enable "fatal_errors = false"
checking. I can do subtransaction in prepare_expr, because it is the
most deep level, but I cannot to use it elsewhere, because I cannot
handle exception and continue with other parts of statement.

Well, you can continue on the next statement. That's almost as good. In
practice, if there's one error in a statement, it seems unlikely that
you would correctly diagnose other errors on the same line. They're more
likely to be fallout of the same mistake.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#54Pavel Stehule
pavel.stehule@gmail.com
In reply to: Heikki Linnakangas (#53)
Re: poll: CHECK TRIGGER?

2012/3/29 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

On 28.03.2012 23:54, Pavel Stehule wrote:

2012/3/28 Heikki Linnakangas<heikki.linnakangas@enterprisedb.com>:

In prepare_expr(), you use a subtransaction to catch any ERRORs that
happen
during parsing the expression. That's a good idea, and I think many of
the
check_* functions could be greatly simplified by adopting a similar
approach. Just ereport() any errors you find, and catch them at the
appropriate level, appending the error to the output string. Your current
approach of returning true/false depending on whether there was any
errors
seems tedious.

This is not possible, when we would to enable "fatal_errors = false"
checking. I can do subtransaction in prepare_expr, because it is the
most deep level, but I cannot to use it elsewhere, because I cannot
handle exception and continue with other parts of statement.

Well, you can continue on the next statement. That's almost as good. In
practice, if there's one error in a statement, it seems unlikely that you
would correctly diagnose other errors on the same line. They're more likely
to be fallout of the same mistake.

no, for example, it means, if I found error in "if_then" statements,
still I would to check "else" statements.

Regards

Pavel

Show quoted text

--
 Heikki Linnakangas
 EnterpriseDB   http://www.enterprisedb.com

#55Pavel Stehule
pavel.stehule@gmail.com
In reply to: Heikki Linnakangas (#51)
1 attachment(s)
Re: poll: CHECK TRIGGER?

Hello

2012/3/28 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

Ok, seems that the API issue is settled, so I'm now looking at the code
actually doing the checking. My first impression is that this is a lot of
code. Can we simplify it?

I played with this and It is not be reduced without darkening current
code of pl_exec.c. So I moved code related to checking from files to
new file pl_check.c. This code is relative large - about 50 kb, but it
is relative simple and I hope it is readable. I afraid so this cannot
be reduced by reuse with other pl_func.c (significantly). Recursive
iteration over nodes is relative not big part of this patch (~25%) and
general stmt walker doesn't help too much.

Since this is deeply integrated into the PL/pgSQL interpreter, I was
expecting that this would run through the normal interpreter, in a special
mode that skips all the actual execution of queries, and shortcuts all loops
and other control statements so that all code is executed only once. That
would mean sprinkling some "if (check_only)" code into the normal exec_*
functions. I'm not sure how invasive that would be, but it's worth
considering. I think you would be able to more easily catch more errors that
way, and the check code would stay better in sync with the execution code.

-1

there are a few places, that are very difficult: basic block with
exception handling - exception handlers, CASE stmt, .... Other issue
is increasing of complexity some routines like exec_eval*

Another thought is that check_stmt() and all its subroutines are very
similar to the plpgsql_dumptree() code. Would it make sense to merge those?
You could have an output mode, in addition to the xml and plain-text
formats, that would just dump the whole tree like plpgsql_dumptree() does.

It is difficult now - without changes in plpgsql_stmt_if,
plpgsql_stmt_case and plpgsql_stmt_block is not possible to write
general walker that is usable for checking and dumptree. It needs
redesign of these nodes first.

In prepare_expr(), you use a subtransaction to catch any ERRORs that happen
during parsing the expression. That's a good idea, and I think many of the
check_* functions could be greatly simplified by adopting a similar
approach. Just ereport() any errors you find, and catch them at the
appropriate level, appending the error to the output string. Your current
approach of returning true/false depending on whether there was any errors
seems tedious.

It cannot be implemented in AST interpret. Without removing some
requested functionality - fatal_errors.

If you create a function with an invalid body (ie. set
check_function_bodies=off; create function ... $$ bogus $$;) ,
plpgsql_check_function() still throws an error. It's understandable that it
cannot do in-depth analysis if the function cannot be parsed, but I would
expect the syntax error to be returned as a return value like other errors
that it complains about, not thrown as a hard ERROR. That would make it more
useful to bulk-check all functions in a database with something like "select
plpgsql_check_function(oid) from pg_class". As it is, the checking stops at
the first invalid function with an error.

done

postgres=> select plpgsql_check_function('sss'::regproc, 0);
plpgsql_check_function
-------------------------------------------------------------
error:42601:syntax error at or near "adasdfsadf"
Query: adasdfsadf
-- ^
Context: compilation of PL/pgSQL function "sss" near line 1
(4 rows)

PS. I think plpgsql_check_function() belongs in pl_handler.c

done

Regards

Pavel Stehule

Show quoted text

--
 Heikki Linnakangas
 EnterpriseDB   http://www.enterprisedb.com

Attachments:

plpgsql_check_function2-2012-03-30.diff.gzapplication/x-gzip; name=plpgsql_check_function2-2012-03-30.diff.gzDownload
#56Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Pavel Stehule (#55)
1 attachment(s)
Re: poll: CHECK TRIGGER?

On 30.03.2012 12:36, Pavel Stehule wrote:

2012/3/28 Heikki Linnakangas<heikki.linnakangas@enterprisedb.com>:

In prepare_expr(), you use a subtransaction to catch any ERRORs that happen
during parsing the expression. That's a good idea, and I think many of the
check_* functions could be greatly simplified by adopting a similar
approach. Just ereport() any errors you find, and catch them at the
appropriate level, appending the error to the output string. Your current
approach of returning true/false depending on whether there was any errors
seems tedious.

It cannot be implemented in AST interpret. Without removing some
requested functionality - fatal_errors.

I don't think I'm getting my point across by explaining, so here's a
modified version of the patch that does what I was trying to say. The
general pattern of the checker functions has been changed. Instead of
returning a boolean indicating error or no error, with checks for
fatal_errors scattered around them, the internal checker functions now
return nothing. Any errors are reported with ereport(), and there is a
PG_TRY/CATCH block in a couple of carefully chosen places: in
check_stmt(), so that if you get an error while checking a statement,
you continue checking on the next statement, and in check_assignment()
which is now used by check_expr() and a few other helper functions to
basically check all expressions and SQL statements.

IMHO this makes the code much more readable, now that the control logic
of when to return and when to continue is largely gone. A lot of other
cleanup still needs to be done, I just wanted to demonstrate this
ereport+try/catch idea with this patch.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachments:

plpgsql_check_function-heikki-2.patchtext/x-diff; name=plpgsql_check_function-heikki-2.patchDownload
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c5e9c2e..9644c33 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -4296,6 +4296,24 @@ $$ LANGUAGE plpgsql;
     provide convenient features such as escaping single quotes and
     making it easier to recreate and debug functions.
    </para>
+   
+  <sect2 id="plpgsql-check-function">
+   <title>Checking of embedded SQL</title>
+   <para>
+    The SQL statements inside <application>PL/pgSQL</> functions are
+    checked by validator for semantic errors. These errors
+    can be found by <function>plpgsql_check_function</function>:
+<programlisting>
+postgres=# select plpgsql_check_function('fx(int)');
+             plpgsql_check_function             
+------------------------------------------------
+ error:42703:3:RETURN:column "b" does not exist
+ Query: SELECT (select a from t1 where b < _a)
+ --                                    ^
+                (3 rows)
+</programlisting>
+   </para>
+  </sect2>
 
   <sect2 id="plpgsql-quote-tips">
    <title>Handling of Quotation Marks</title>
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 751a98d..e13e38c 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -17,9 +17,9 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
 SHLIB_LINK = $(filter -lintl, $(LIBS))
 rpath =
 
-OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o
+OBJS = pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o pl_check.o
 
-DATA = plpgsql.control plpgsql--1.0.sql plpgsql--unpackaged--1.0.sql
+DATA = plpgsql.control plpgsql--1.1.sql plpgsql--unpackaged--1.1.sql
 
 all: all-lib
 
@@ -44,7 +44,7 @@ uninstall-data:
 
 
 # Force these dependencies to be known even without dependency info built:
-pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl_gram.h plerrcodes.h
+pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o pl_check.o : plpgsql.h pl_gram.h plerrcodes.h
 
 # See notes in src/backend/parser/Makefile about the following two rules
 
diff --git a/src/pl/plpgsql/src/pl_check.c b/src/pl/plpgsql/src/pl_check.c
new file mode 100644
index 0000000..674440a
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_check.c
@@ -0,0 +1,1694 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_checker.c		- Checker for the PL/pgSQL
+ *			  procedural language
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/pl/plpgsql/src/pl_check.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "plpgsql.h"
+
+#include "funcapi.h"
+#include "miscadmin.h"
+
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "executor/spi_priv.h"
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+#include "utils/rel.h"
+#include "utils/xml.h"
+
+static void check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec);
+static void check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr);
+static void assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate,
+					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+								    TupleDesc tupdesc);
+static void assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc);
+static TupleDesc expr_get_desc(PLpgSQL_checkstate *cstate,
+						PLpgSQL_expr *query,
+							bool use_element_type,
+							bool expand_record,
+								bool is_expression);
+static void var_init_to_null(PLpgSQL_checkstate *cstate, int varno);
+static void check_stmts(PLpgSQL_checkstate *cstate, List *stmts);
+static void check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt);
+static void prepare_expr(PLpgSQL_checkstate *cstate,
+				  PLpgSQL_expr *expr, int cursorOptions);
+static void check_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
+				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
+				 int targetdno);
+static void check_element_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
+				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
+				 int targetdno);
+static void check_assignment_guts(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
+				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
+								  int targetdno, bool use_element_type, bool is_expression);
+static void checker_error_edata(PLpgSQL_checkstate *cstate, ErrorData *edata);
+
+static void checker_error(PLpgSQL_checkstate *cstate,
+						 int sqlerrcode,
+						 const char *message, const char *detail,
+						 const char *hint,const char *level,
+						 int position, const char *query,
+						 const char *context);
+
+static void cstate_setup(PLpgSQL_checkstate *cstate,
+					   TupleDesc tupdesc, Tuplestorestate *tupstore,
+					   bool fatal_errors, int format);
+static void cstate_flush(PLpgSQL_checkstate *cstate);
+static void destroy_cstate(PLpgSQL_checkstate *cstate);
+static void function_check(PLpgSQL_function *func, FunctionCallInfo fcinfo,
+					   PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate);
+static void trigger_check(PLpgSQL_function *func,
+					  TriggerData *trigdata,
+					  PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate);
+static int load_configuration(HeapTuple procTuple, bool *reload_config);
+
+/*
+ * top checker function
+ *
+ */
+void
+plpgsql_function_check(HeapTuple procTuple, Oid relid,
+					    TupleDesc tupdesc,
+					    Tuplestorestate *tupstore,
+							    bool fatal_errors, int format)
+{
+	PLpgSQL_checkstate cstate;
+	PLpgSQL_function * volatile function = NULL;
+	int		save_nestlevel = 0;
+	bool		reload_config;
+	Oid	funcoid;
+	FunctionCallInfoData 		fake_fcinfo;
+	FmgrInfo			flinfo;
+	TriggerData 			trigdata;
+	int			rc;
+	ResourceOwner		oldowner;
+	MemoryContext		oldCxt;
+	PLpgSQL_execstate *cur_estate = NULL;
+
+	funcoid = HeapTupleGetOid(procTuple);
+
+	/*
+	 * Connect to SPI manager
+	 */
+	if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+			elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+
+	plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, &trigdata, funcoid, OidIsValid(relid));
+	cstate_setup(&cstate, tupdesc, tupstore, fatal_errors, format);
+
+	if (OidIsValid(relid))
+		trigdata.tg_relation = relation_open(relid, AccessShareLock);
+
+	oldCxt = CurrentMemoryContext;
+	oldowner = CurrentResourceOwner;
+
+	PG_TRY();
+	{
+		PLpgSQL_execstate estate;
+
+		BeginInternalSubTransaction(NULL);
+		MemoryContextSwitchTo(oldCxt);
+
+		save_nestlevel = load_configuration(procTuple, &reload_config);
+
+		/* Get a compiled function */
+		function = plpgsql_compile(&fake_fcinfo, false);
+
+		plpgsql_estate_setup(&estate, function, (ReturnSetInfo *) fake_fcinfo.resultinfo);
+		cstate.estate = &estate;
+
+		/* Must save and restore prior value of cur_estate */
+		cur_estate = function->cur_estate;
+
+		/* Mark the function as busy, so it can't be deleted from under us */
+		function->use_count++;
+
+		/* Create a fake runtime environment and process check */
+		if (!OidIsValid(relid))
+			function_check(function, &fake_fcinfo, &estate, &cstate);
+		else
+			trigger_check(function, &trigdata, &estate, &cstate);
+
+		/* reload back a GUC. XXX: isn't this done automatically by subxact rollback? */
+		if (reload_config)
+			AtEOXact_GUC(true, save_nestlevel);
+
+		plpgsql_destroy_econtext(&estate);
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldCxt);
+		CurrentResourceOwner = oldowner;
+
+		SPI_restore_connection();
+	}
+	PG_CATCH();
+	{
+		ErrorData *edata;
+
+		MemoryContextSwitchTo(oldCxt);
+		edata = CopyErrorData();
+		FlushErrorState();
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldCxt);
+		CurrentResourceOwner = oldowner;
+
+		checker_error_edata(&cstate, edata);
+		MemoryContextSwitchTo(oldCxt);
+		/* reconnect spi */
+		SPI_restore_connection();
+	}
+	PG_END_TRY();
+
+	if (function)
+	{
+		function->cur_estate = cur_estate;
+		function->use_count--;
+
+		/*
+		 * We cannot to preserve instance of this function, because
+		 * expressions are not consistent - a tests on simple expression
+		 * was be processed newer.
+		 */
+		plpgsql_delete_function(function);
+	}
+
+	if (OidIsValid(relid))
+		relation_close(trigdata.tg_relation, AccessShareLock);
+
+	cstate_flush(&cstate);
+
+	/* Cleanup temporary memory */
+	destroy_cstate(&cstate);
+
+	/*
+	 * Disconnect from SPI manager
+	 */
+	if ((rc = SPI_finish()) != SPI_OK_FINISH)
+			elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+}
+
+/*
+ * Check function - it prepare variables and starts a prepare plan walker
+ */
+static void
+function_check(PLpgSQL_function *func, FunctionCallInfo fcinfo,
+					   PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate)
+{
+	int			i;
+
+	/*
+	 * Make local execution copies of all the datums
+	 */
+	for (i = 0; i < cstate->estate->ndatums; i++)
+		cstate->estate->datums[i] = plpgsql_copy_plpgsql_datum(func->datums[i]);
+
+	/*
+	 * Store the actual call argument values into the appropriate variables
+	 */
+	for (i = 0; i < func->fn_nargs; i++)
+	{
+		int			n = func->fn_argvarnos[i];
+
+		switch (cstate->estate->datums[n]->dtype)
+		{
+			case PLPGSQL_DTYPE_VAR:
+				{
+					var_init_to_null(cstate, n);
+				}
+				break;
+
+			case PLPGSQL_DTYPE_ROW:
+				{
+					PLpgSQL_row *row = (PLpgSQL_row *) cstate->estate->datums[n];
+
+					plpgsql_exec_move_row(cstate->estate, NULL, row, NULL, NULL);
+				}
+				break;
+
+			default:
+				elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+		}
+	}
+
+	/*
+	 * Now check the toplevel block of statements
+	 */
+	check_stmt(cstate, (PLpgSQL_stmt *) func->action);
+}
+
+/*
+ * Check trigger - prepare fake environments for testing trigger
+ *
+ */
+static void
+trigger_check(PLpgSQL_function *func, TriggerData *trigdata,
+					  PLpgSQL_execstate *estate, PLpgSQL_checkstate *cstate)
+{
+	PLpgSQL_rec *rec_new,
+				*rec_old;
+	int			i;
+
+	/*
+	 * Make local execution copies of all the datums
+	 */
+	for (i = 0; i < cstate->estate->ndatums; i++)
+		cstate->estate->datums[i] = plpgsql_copy_plpgsql_datum(func->datums[i]);
+
+	/*
+	 * Put the OLD and NEW tuples into record variables
+	 *
+	 * We make the tupdescs available in both records even though only one may
+	 * have a value.  This allows parsing of record references to succeed in
+	 * functions that are used for multiple trigger types.	For example, we
+	 * might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')",
+	 * which should parse regardless of the current trigger type.
+	 */
+	rec_new = (PLpgSQL_rec *) (cstate->estate->datums[func->new_varno]);
+	rec_new->freetup = false;
+	rec_new->freetupdesc = false;
+	assign_tupdesc_row_or_rec(cstate, NULL, rec_new, trigdata->tg_relation->rd_att);
+
+	rec_old = (PLpgSQL_rec *) (cstate->estate->datums[func->old_varno]);
+	rec_old->freetup = false;
+	rec_old->freetupdesc = false;
+	assign_tupdesc_row_or_rec(cstate, NULL, rec_old, trigdata->tg_relation->rd_att);
+
+	/*
+	 * Assign the special tg_ variables
+	 */
+	var_init_to_null(cstate, func->tg_op_varno);
+	var_init_to_null(cstate, func->tg_name_varno);
+	var_init_to_null(cstate, func->tg_when_varno);
+	var_init_to_null(cstate, func->tg_level_varno);
+	var_init_to_null(cstate, func->tg_relid_varno);
+	var_init_to_null(cstate, func->tg_relname_varno);
+	var_init_to_null(cstate, func->tg_table_name_varno);
+	var_init_to_null(cstate, func->tg_table_schema_varno);
+	var_init_to_null(cstate, func->tg_nargs_varno);
+	var_init_to_null(cstate, func->tg_argv_varno);
+
+	/*
+	 * Now check the toplevel block of statements
+	 */
+	check_stmt(cstate, (PLpgSQL_stmt *) func->action);
+}
+
+/*
+ * Verify lvalue
+ *    It doesn't repeat a checks that are done.
+ *  Checks a subscript expressions, verify a validity of record's fields,
+ *  Returns true, when target is valid
+ */
+static void
+check_target(PLpgSQL_checkstate *cstate, int varno)
+{
+	PLpgSQL_datum *target = cstate->estate->datums[varno];
+
+	switch (target->dtype)
+	{
+		case PLPGSQL_DTYPE_VAR:
+		case PLPGSQL_DTYPE_REC:
+			/* nothing to check */
+			break;
+
+		case PLPGSQL_DTYPE_ROW:
+			check_row_or_rec(cstate, (PLpgSQL_row *) target, NULL);
+			break;
+
+		case PLPGSQL_DTYPE_RECFIELD:
+			{
+				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+				PLpgSQL_rec *rec;
+				int			fno;
+
+				rec = (PLpgSQL_rec *) (cstate->estate->datums[recfield->recparentno]);
+
+				/*
+				 * Check that there is already a tuple in the record. We need
+				 * that because records don't have any predefined field
+				 * structure.
+				 */
+				if (!HeapTupleIsValid(rec->tup))
+					ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("record \"%s\" is not assigned to tuple structure",
+									rec->refname)));
+
+				/*
+				 * Get the number of the records field to change and the
+				 * number of attributes in the tuple.  Note: disallow system
+				 * column names because the code below won't cope.
+				 */
+				fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+				if (fno <= 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("record \"%s\" has no field \"%s\"",
+									rec->refname, recfield->fieldname)));
+			}
+			break;
+
+		case PLPGSQL_DTYPE_ARRAYELEM:
+			{
+				/*
+				 * Target is an element of an array
+				 */
+				int			nsubscripts;
+				Oid		arrayelemtypeid;
+				Oid		arraytypeid;
+
+				/*
+				 * To handle constructs like x[1][2] := something, we have to
+				 * be prepared to deal with a chain of arrayelem datums. Chase
+				 * back to find the base array datum, and save the subscript
+				 * expressions as we go.  (We are scanning right to left here,
+				 * but want to evaluate the subscripts left-to-right to
+				 * minimize surprises.)
+				 */
+				nsubscripts = 0;
+				do
+				{
+					PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
+
+					if (nsubscripts++ >= MAXDIM)
+						ereport(ERROR,
+								(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+								 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+										nsubscripts + 1, MAXDIM)));
+
+					/* Validate expression. */
+					/* XXX is_expression */
+					check_expr(cstate, arrayelem->subscript);
+
+					target = cstate->estate->datums[arrayelem->arrayparentno];
+				} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
+
+				/* If target is domain over array, reduce to base type */
+				arraytypeid = exec_get_datum_type(cstate->estate, target);
+				arraytypeid = getBaseType(arraytypeid);
+
+				arrayelemtypeid = get_element_type(arraytypeid);
+
+				if (!OidIsValid(arrayelemtypeid))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("subscripted object is not an array")));
+			}
+			break;
+	}
+}
+
+/*
+ * Check composed lvalue
+ *    There is nothing to check on rec variables
+ */
+static void
+check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec)
+{
+	int			fnum;
+
+	/* there are nothing to check on rec now */
+	if (row != NULL)
+	{
+		for (fnum = 0; fnum < row->nfields; fnum++)
+		{
+			/* skip dropped columns */
+			if (row->varnos[fnum] < 0)
+				continue;
+
+			check_target(cstate, row->varnos[fnum]);
+		}
+	}
+}
+
+/*
+ * Generate a prepared plan - this is simplified copy from pl_exec.c
+ *   Is not necessary to check simple plan,
+ * returns true, when expression is succesfully prepared.
+ */
+static void
+prepare_expr(PLpgSQL_checkstate *cstate,
+			 PLpgSQL_expr *expr, int cursorOptions)
+{
+	SPIPlanPtr plan;
+
+	if (expr->plan != NULL)
+		return;	/* already checked */
+
+	/*
+	 * The grammar can't conveniently set expr->func while building the parse
+	 * tree, so make sure it's set before parser hooks need it.
+	 */
+	expr->func = cstate->estate->func;
+
+	/*
+	 * Generate and save the plan
+	 */
+	plan = SPI_prepare_params(expr->query,
+							  (ParserSetupHook) plpgsql_parser_setup,
+							  (void *) expr,
+							  cursorOptions);
+
+	if (plan == NULL)
+	{
+		/* Some SPI errors deserve specific error messages */
+		switch (SPI_result)
+		{
+			case SPI_ERROR_COPY:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot COPY to/from client in PL/pgSQL")));
+				break;
+
+			case SPI_ERROR_TRANSACTION:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot begin/end transactions in PL/pgSQL"),
+						 errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
+				break;
+
+			default:
+				elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
+					 expr->query, SPI_result_code_string(SPI_result));
+		}
+	}
+
+	expr->plan = SPI_saveplan(plan);
+	SPI_freeplan(plan);
+}
+
+/*
+ * Verify a expression
+ */
+static void
+check_expr(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr)
+{
+	if (expr)
+		check_assignment_guts(cstate, expr, NULL, NULL, -1, false, false);
+}
+
+/*
+ * Verify an assignment of 'expr' to 'target'
+ */
+static void
+check_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
+					  PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
+					  int targetdno)
+{
+	bool		is_expression = (targetrec == NULL && targetrow == NULL);
+
+	check_assignment_guts(cstate, expr, targetrec, targetrow, targetdno, false,
+						  is_expression);
+}
+
+static void
+check_element_assignment(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
+				 PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
+				 int targetdno)
+{
+	bool		is_expression = (targetrec == NULL && targetrow == NULL);
+
+	check_assignment_guts(cstate, expr, targetrec, targetrow, targetdno, true,
+						  is_expression);
+}
+
+static void
+check_assignment_guts(PLpgSQL_checkstate *cstate, PLpgSQL_expr *expr,
+					  PLpgSQL_rec *targetrec, PLpgSQL_row *targetrow,
+					  int targetdno, bool use_element_type, bool is_expression)
+{
+	ResourceOwner		oldowner;
+	MemoryContext		oldCxt = CurrentMemoryContext;
+	TupleDesc tupdesc;
+
+	oldowner = CurrentResourceOwner;
+	BeginInternalSubTransaction(NULL);
+	MemoryContextSwitchTo(oldCxt);
+
+	PG_TRY();
+	{
+		prepare_expr(cstate, expr, 0);
+		tupdesc = expr_get_desc(cstate, expr, use_element_type, true, is_expression);
+		if (tupdesc)
+		{
+			if (targetrow != NULL || targetrec != NULL)
+				assign_tupdesc_row_or_rec(cstate, targetrow, targetrec, tupdesc);
+			if (targetdno != -1)
+				assign_tupdesc_dno(cstate, targetdno, tupdesc);
+
+			if (targetrow)
+			{
+				if (targetrow->nfields > tupdesc->natts)
+					checker_error(cstate,
+								  0,
+								  "too few attributies for target variables",
+								  "There are more target variables than output columns in query.",
+								  "Check target variables in SELECT INTO statement.",
+								  "warning",
+								  0, NULL, NULL);
+				else if (targetrow->nfields < tupdesc->natts)
+					checker_error(cstate,
+								  0,
+								  "too many attributies for target variables",
+								  "There are less target variables than output columns in query.",
+								  "Check target variables in SELECT INTO statement",
+								  "warning",
+								  0, NULL, NULL);
+			}
+
+			ReleaseTupleDesc(tupdesc);
+		}
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldCxt);
+		CurrentResourceOwner = oldowner;
+
+		SPI_restore_connection();
+	}
+	PG_CATCH();
+	{
+		ErrorData	*edata;
+
+		MemoryContextSwitchTo(oldCxt);
+		edata = CopyErrorData();
+		FlushErrorState();
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldCxt);
+		CurrentResourceOwner = oldowner;
+
+		/*
+		 * If fatal_errors is true, we just propagate the error up to the
+		 * highest level. Otherwise the error is appended to our current
+		 * list of errors, and we continue checking.
+		 */
+		if (cstate->fatal_errors)
+			ReThrowError(edata);
+		else
+			checker_error_edata(cstate, edata);
+		MemoryContextSwitchTo(oldCxt);
+
+		/* reconnect spi */
+		SPI_restore_connection();
+	}
+	PG_END_TRY();
+}
+
+/*
+ * We have to assign TupleDesc to all used record variables step by step.
+ * We would to use a exec routines for query preprocessing, so we must
+ * to create a typed NULL value, and this value is assigned to record
+ * variable.
+ */
+static void
+assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate,
+					    PLpgSQL_row *row, PLpgSQL_rec *rec,
+								    TupleDesc tupdesc)
+{
+	bool	   *nulls;
+	HeapTuple  tup;
+
+	if (tupdesc == NULL)
+	{
+		checker_error(cstate,
+					 0,
+					 "tuple descriptor is empty", NULL, NULL,
+					 "warning",
+					 0, NULL, NULL);
+		return;
+	}
+
+	/*
+	 * row variable has assigned TupleDesc already, so don't be processed
+	 * here
+	 */
+	if (rec != NULL)
+	{
+		PLpgSQL_rec *target = (PLpgSQL_rec *)(cstate->estate->datums[rec->dno]);
+
+		if (target->freetup)
+			heap_freetuple(target->tup);
+
+		if (rec->freetupdesc)
+			FreeTupleDesc(target->tupdesc);
+
+		/* initialize rec by NULLs */
+		nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
+		memset(nulls, true, tupdesc->natts * sizeof(bool));
+
+		target->tupdesc = CreateTupleDescCopy(tupdesc);
+		target->freetupdesc = true;
+
+		tup = heap_form_tuple(tupdesc, NULL, nulls);
+		if (HeapTupleIsValid(tup))
+		{
+			target->tup = tup;
+			target->freetup = true;
+		}
+		else
+			elog(ERROR, "cannot to build valid composite value");
+	}
+}
+
+/*
+ * Assign a tuple descriptor to variable specified by dno
+ */
+static void
+assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc)
+{
+	PLpgSQL_datum *target = cstate->estate->datums[varno];
+
+	if (target->dtype == PLPGSQL_DTYPE_REC)
+		assign_tupdesc_row_or_rec(cstate, NULL, (PLpgSQL_rec *) target, tupdesc);
+}
+
+/*
+ * Returns a tuple descriptor based on existing plan,
+ *  When error is detected returns null.
+ */
+static TupleDesc 
+expr_get_desc(PLpgSQL_checkstate *cstate,
+			  PLpgSQL_expr *query,
+			  bool use_element_type,
+			  bool expand_record,
+			  bool is_expression)
+{
+	TupleDesc tupdesc;
+	CachedPlanSource *plansource;
+
+	if (query->plan != NULL)
+	{
+		SPIPlanPtr plan = query->plan;
+
+		if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+			elog(ERROR, "cached plan is not valid plan");
+
+		if (list_length(plan->plancache_list) != 1)
+			elog(ERROR, "plan is not single execution plan");
+
+		plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+
+		if (!plansource->resultDesc)
+		{
+			if (is_expression)
+				elog(ERROR, "query returns no result");
+			else
+				return NULL;
+		}
+		tupdesc = CreateTupleDescCopy(plansource->resultDesc);
+	}
+	else
+		elog(ERROR, "there are no plan for query: \"%s\"",
+							    query->query);
+
+	/*
+	 * try to get a element type, when result is a array (used with FOREACH ARRAY stmt)
+	 */
+	if (use_element_type)
+	{
+		Oid elemtype;
+		TupleDesc elemtupdesc;
+
+		/* result should be a array */
+		if (is_expression && tupdesc->natts != 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("query \"%s\" returned %d columns",
+							query->query,
+							tupdesc->natts)));
+
+		/* check the type of the expression - must be an array */
+		elemtype = get_element_type(tupdesc->attrs[0]->atttypid);
+		if (!OidIsValid(elemtype))
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("FOREACH expression must yield an array, not type %s",
+							format_type_be(tupdesc->attrs[0]->atttypid))));
+			FreeTupleDesc(tupdesc);
+		}
+
+		/* we can't know typmod now */
+		elemtupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true);
+		if (elemtupdesc != NULL)
+		{
+			FreeTupleDesc(tupdesc);
+			tupdesc = CreateTupleDescCopy(elemtupdesc);
+			ReleaseTupleDesc(elemtupdesc);
+		}
+		else
+			/* XXX: should be a warning? */
+			ereport(ERROR,
+					(errmsg("cannot to identify real type for record type variable")));
+	}
+
+	if (is_expression && tupdesc->natts != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("query \"%s\" returned %d columns",
+						 query->query,
+						tupdesc->natts)));
+
+	/*
+	 * One spacial case is when record is assigned to composite type, then 
+	 * we should to unpack composite type.
+	 */
+	if (tupdesc->tdtypeid == RECORDOID &&
+		tupdesc->tdtypmod == -1 &&
+		tupdesc->natts == 1 && expand_record)
+	{
+		TupleDesc unpack_tupdesc;
+
+		unpack_tupdesc = lookup_rowtype_tupdesc_noerror(tupdesc->attrs[0]->atttypid,
+											tupdesc->attrs[0]->atttypmod,
+														true);
+		if (unpack_tupdesc != NULL)
+		{
+			FreeTupleDesc(tupdesc);
+			tupdesc = CreateTupleDescCopy(unpack_tupdesc);
+			ReleaseTupleDesc(unpack_tupdesc);
+		}
+	}
+
+	/*
+	 * There is special case, when returned tupdesc contains only
+	 * unpined record: rec := func_with_out_parameters(). IN this case
+	 * we must to dig more deep - we have to find oid of function and
+	 * get their parameters,
+	 *
+	 * This is support for assign statement
+	 *     recvar := func_with_out_parameters(..)
+	 *
+	 * XXX: Why don't we always do that?
+	 */
+	if (tupdesc->tdtypeid == RECORDOID &&
+		tupdesc->tdtypmod == -1 &&
+		tupdesc->natts == 1 &&
+		tupdesc->attrs[0]->atttypid == RECORDOID &&
+		tupdesc->attrs[0]->atttypmod == -1 &&
+		expand_record)
+	{
+		PlannedStmt *_stmt;
+		Plan		*_plan;
+		TargetEntry *tle;
+		CachedPlan *cplan;
+
+		/*
+		 * When tupdesc is related to unpined record, we will try
+		 * to check plan if it is just function call and if it is
+		 * then we can try to derive a tupledes from function's
+		 * description.
+		 */
+		cplan = GetCachedPlan(plansource, NULL, true);
+		_stmt = (PlannedStmt *) linitial(cplan->stmt_list);
+
+		if (IsA(_stmt, PlannedStmt) && _stmt->commandType == CMD_SELECT)
+		{
+			_plan = _stmt->planTree;
+			if (IsA(_plan, Result) && list_length(_plan->targetlist) == 1)
+			{
+				tle = (TargetEntry *) linitial(_plan->targetlist);
+				if (((Node *) tle->expr)->type == T_FuncExpr)
+				{
+					FuncExpr *fn = (FuncExpr *) tle->expr;
+					FmgrInfo flinfo;
+					FunctionCallInfoData fcinfo;
+					TupleDesc rd;
+					Oid		rt;
+
+					fmgr_info(fn->funcid, &flinfo);
+					flinfo.fn_expr = (Node *) fn;
+					fcinfo.flinfo = &flinfo;
+
+					get_call_result_type(&fcinfo, &rt, &rd);
+					if (rd == NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_DATATYPE_MISMATCH),
+								 errmsg("function does not return composite type, is not possible to identify composite type")));
+
+					FreeTupleDesc(tupdesc);
+					BlessTupleDesc(rd);
+
+					tupdesc = rd;
+				}
+			}
+		}
+
+		ReleaseCachedPlan(cplan, true);
+	}
+
+	return tupdesc;
+}
+
+/*
+ * Ensure check for all statements in list
+ */
+void
+check_stmts(PLpgSQL_checkstate *cstate, List *stmts)
+{
+	ListCell *lc;
+
+	foreach(lc, stmts)
+	{
+		check_stmt(cstate, (PLpgSQL_stmt *) lfirst(lc));
+	}
+}
+
+/*
+ * walk over all statements
+ */
+void
+check_stmt(PLpgSQL_checkstate *cstate, PLpgSQL_stmt *stmt)
+{
+	TupleDesc	tupdesc = NULL;
+	PLpgSQL_function *func;
+	ListCell *l;
+	ResourceOwner		oldowner;
+	MemoryContext		oldCxt = CurrentMemoryContext;
+
+	if (stmt == NULL)
+		return;
+
+	cstate->estate->err_stmt = stmt;
+	func = cstate->estate->func;
+
+	oldowner = CurrentResourceOwner;
+	BeginInternalSubTransaction(NULL);
+	MemoryContextSwitchTo(oldCxt);
+
+	PG_TRY();
+	{
+		switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
+		{
+			case PLPGSQL_STMT_BLOCK:
+				{
+					PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt;
+					int		i;
+					PLpgSQL_datum		*d;
+
+					for (i = 0; i < stmt_block->n_initvars; i++)
+					{
+						d = func->datums[stmt_block->initvarnos[i]];
+
+						if (d->dtype == PLPGSQL_DTYPE_VAR)
+						{
+							PLpgSQL_var *var = (PLpgSQL_var *) d;
+
+							check_expr(cstate, var->default_val);
+						}
+					}
+
+					check_stmts(cstate, stmt_block->body);
+
+					if (stmt_block->exceptions)
+					{
+						foreach(l, stmt_block->exceptions->exc_list)
+						{
+							check_stmts(cstate, ((PLpgSQL_exception *) lfirst(l))->action);
+						}
+					}
+				}
+				break;
+
+			case PLPGSQL_STMT_ASSIGN:
+				{
+					PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+
+					check_target(cstate, stmt_assign->varno);
+
+					/* prepare plan if desn't exist yet */
+					check_assignment(cstate, stmt_assign->expr, NULL, NULL,
+									 stmt_assign->varno);
+					/* XXX: i thínk I lost some args to prepare_expr here */
+				}
+				break;
+
+			case PLPGSQL_STMT_IF:
+				{
+					PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
+					ListCell *l;
+
+					check_expr(cstate, stmt_if->cond);
+					check_stmts(cstate, stmt_if->then_body);
+					foreach(l, stmt_if->elsif_list)
+					{
+						PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+
+						check_expr(cstate, elif->cond);
+						check_stmts(cstate, elif->stmts);
+					}
+
+					check_stmts(cstate, stmt_if->else_body);
+				}
+				break;
+
+			case PLPGSQL_STMT_CASE:
+				{
+					PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
+					Oid result_oid;
+
+					if (stmt_case->t_expr != NULL)
+					{
+						PLpgSQL_var *t_var = (PLpgSQL_var *) cstate->estate->datums[stmt_case->t_varno];
+
+						/* we need to set hidden variable type */
+						prepare_expr(cstate, stmt_case->t_expr, 0);
+						tupdesc = expr_get_desc(cstate,
+												stmt_case->t_expr,
+												false,		/* no element type */
+												true,		/* expand record */
+												true);		/* is expression */
+						result_oid = tupdesc->attrs[0]->atttypid;
+
+						/*
+						 * When expected datatype is different from real,
+						 * change it. Note that what we're modifying here is
+						 * an execution copy of the datum, so this doesn't
+						 * affect the originally stored function parse tree.
+						 */
+						if (t_var->datatype->typoid != result_oid)
+							t_var->datatype = plpgsql_build_datatype(result_oid,
+																	 -1,
+																	 cstate->estate->func->fn_input_collation);
+						ReleaseTupleDesc(tupdesc);
+					}
+
+					foreach(l, stmt_case->case_when_list)
+					{
+						PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+
+						check_expr(cstate, cwt->expr);
+						check_stmts(cstate, cwt->stmts);
+					}
+
+					check_stmts(cstate, stmt_case->else_stmts);
+				}
+				break;
+
+			case PLPGSQL_STMT_LOOP:
+				check_stmts(cstate, ((PLpgSQL_stmt_loop *) stmt)->body);
+				break;
+
+			case PLPGSQL_STMT_WHILE:
+				{
+					PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
+
+					check_expr(cstate, stmt_while->cond);
+					check_stmts(cstate, stmt_while->body);
+				}
+				break;
+
+			case PLPGSQL_STMT_FORI:
+				{
+					PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
+
+					check_expr(cstate, stmt_fori->lower);
+					check_expr(cstate, stmt_fori->upper);
+					check_expr(cstate, stmt_fori->step);
+					check_stmts(cstate, stmt_fori->body);
+				}
+				break;
+
+			case PLPGSQL_STMT_FORS:
+				{
+					PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+
+					check_row_or_rec(cstate, stmt_fors->row, stmt_fors->rec);
+
+					/* we need to set hidden variable type */
+					check_assignment(cstate, stmt_fors->query,
+									 stmt_fors->rec, stmt_fors->row, -1);
+
+					check_stmts(cstate, stmt_fors->body);
+				}
+				break;
+
+		case PLPGSQL_STMT_FORC:
+				{
+					PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+					PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
+
+					check_row_or_rec(cstate, stmt_forc->row, stmt_forc->rec);
+
+					check_expr(cstate, stmt_forc->argquery);
+
+					if (var->cursor_explicit_expr != NULL)
+						check_assignment(cstate, var->cursor_explicit_expr,
+										 stmt_forc->rec, stmt_forc->row, -1);
+
+					check_stmts(cstate, stmt_forc->body);
+				}
+				break;
+
+		case PLPGSQL_STMT_DYNFORS:
+				{
+					PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+
+					if (stmt_dynfors->rec != NULL)
+					{
+						checker_error(cstate,
+									  0,
+									  "cannot determinate a result of dynamic SQL", 
+									  "Cannot to contine in check.",
+									  "Don't use dynamic SQL and record type together, when you would check function.",
+									  "warning",
+									  0, NULL, NULL);
+
+						/*
+						 * don't continue in checking. Behave should be indeterministic.
+						 */
+						break;
+					}
+
+					check_expr(cstate, stmt_dynfors->query);
+
+					foreach(l, stmt_dynfors->params)
+					{
+						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
+					}
+
+					check_stmts(cstate, stmt_dynfors->body);
+				}
+				break;
+
+			case PLPGSQL_STMT_FOREACH_A:
+				{
+					PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+
+					check_target(cstate, stmt_foreach_a->varno);
+
+					check_element_assignment(cstate,stmt_foreach_a->expr, NULL, NULL, stmt_foreach_a->varno);
+
+					check_stmts(cstate, stmt_foreach_a->body);
+				}
+				break;
+
+			case PLPGSQL_STMT_EXIT:
+				check_expr(cstate, ((PLpgSQL_stmt_exit *) stmt)->cond);
+				break;
+
+			case PLPGSQL_STMT_PERFORM:
+				check_expr(cstate, ((PLpgSQL_stmt_perform *) stmt)->expr);
+				break;
+
+			case PLPGSQL_STMT_RETURN:
+				check_expr(cstate, ((PLpgSQL_stmt_return *) stmt)->expr);
+				break;
+
+			case PLPGSQL_STMT_RETURN_NEXT:
+				check_expr(cstate, ((PLpgSQL_stmt_return_next *) stmt)->expr);
+				break;
+
+			case PLPGSQL_STMT_RETURN_QUERY:
+				{
+					PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt;
+
+					check_expr(cstate, stmt_rq->dynquery);
+
+					check_expr(cstate, stmt_rq->query);
+
+					foreach(l, stmt_rq->params)
+					{
+						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
+					}
+				}
+				break;
+			
+			case PLPGSQL_STMT_RAISE:
+				{
+					PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt;
+					ListCell *current_param;
+					char *cp;
+
+					foreach(l, stmt_raise->params)
+					{
+						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
+					}
+
+					foreach(l, stmt_raise->options)
+					{
+						PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(l);
+
+						check_expr(cstate, opt->expr);
+					}
+
+					current_param = list_head(stmt_raise->params);
+
+					/* ensure any single % has a own parameter */
+					if (stmt_raise->message != NULL)
+					{
+						for (cp = stmt_raise->message; *cp; cp++)
+						{
+							if (cp[0] == '%')
+							{
+								if (cp[1] == '%')
+								{
+									cp++;
+									continue;
+								}
+
+								if (current_param == NULL)
+									ereport(ERROR,
+											(errcode(ERRCODE_SYNTAX_ERROR),
+											 errmsg("too few parameters specified for RAISE")));
+
+								current_param = lnext(current_param);
+							}
+						}
+					}
+
+					if (current_param != NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("too many parameters specified for RAISE")));
+				}
+				break;
+
+			case PLPGSQL_STMT_EXECSQL:
+				{
+					PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt;
+
+					if (stmt_execsql->into)
+					{
+						check_row_or_rec(cstate, stmt_execsql->row, stmt_execsql->rec);
+						check_assignment(cstate, stmt_execsql->sqlstmt,
+										 stmt_execsql->rec, stmt_execsql->row, -1);
+					}
+					else
+					{
+						/* only statement */
+						check_expr(cstate, stmt_execsql->sqlstmt);
+					}
+				}
+				break;
+
+			case PLPGSQL_STMT_DYNEXECUTE:
+				{
+					PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+
+					check_expr(cstate, stmt_dynexecute->query);
+
+					foreach(l, stmt_dynexecute->params)
+					{
+						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
+					}
+
+					if (stmt_dynexecute->into)
+					{
+						check_row_or_rec(cstate, stmt_dynexecute->row, stmt_dynexecute->rec);
+
+						if (stmt_dynexecute->rec != NULL)
+						{
+							checker_error(cstate,
+										  0,
+										  "cannot determinate a result of dynamic SQL", 
+										  "Cannot to contine in check.",
+										  "Don't use dynamic SQL and record type together, when you would check function.",
+										  "warning",
+										  0, NULL, NULL);
+							
+							/*
+							 * don't continue in checking. Behave should be indeterministic.
+							 */
+							break;
+						}
+					}
+				}
+				break;
+
+			case PLPGSQL_STMT_OPEN:
+				{
+					PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
+					PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_open->curvar];
+
+					if (var->cursor_explicit_expr)
+						check_expr(cstate, var->cursor_explicit_expr);
+
+					check_expr(cstate, stmt_open->query);
+					check_expr(cstate, stmt_open->argquery);
+					check_expr(cstate, stmt_open->dynquery);
+					foreach(l, stmt_open->params)
+					{
+						check_expr(cstate, (PLpgSQL_expr *) lfirst(l));
+					}
+				}
+				break;
+
+			case PLPGSQL_STMT_GETDIAG:
+				{
+					PLpgSQL_stmt_getdiag *stmt_getdiag = (PLpgSQL_stmt_getdiag *) stmt;
+					ListCell *lc;
+
+					foreach(lc, stmt_getdiag->diag_items)
+					{
+						PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
+
+						check_target(cstate, diag_item->target);
+					}
+				}
+				break;
+
+			case PLPGSQL_STMT_FETCH:
+				{
+					PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+					PLpgSQL_var *var = (PLpgSQL_var *)(cstate->estate->datums[stmt_fetch->curvar]);
+
+					check_row_or_rec(cstate, stmt_fetch->row, stmt_fetch->rec);
+
+					if (var != NULL && var->cursor_explicit_expr != NULL)
+						check_assignment(cstate, var->cursor_explicit_expr,
+										 stmt_fetch->rec, stmt_fetch->row, -1);
+				}
+				break;
+
+			case PLPGSQL_STMT_CLOSE:
+				break;
+
+			default:
+				elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+		}
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldCxt);
+		CurrentResourceOwner = oldowner;
+
+		SPI_restore_connection();
+	}
+	PG_CATCH();
+	{
+		ErrorData	*edata;
+
+		MemoryContextSwitchTo(oldCxt);
+		edata = CopyErrorData();
+		FlushErrorState();
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldCxt);
+		CurrentResourceOwner = oldowner;
+
+		/*
+		 * If fatal_errors is true, we just propagate the error up to the
+		 * highest level. Otherwise the error is appended to our current
+		 * list of errors, and we continue checking.
+		 */
+		if (cstate->fatal_errors)
+			ReThrowError(edata);
+		else
+			checker_error_edata(cstate, edata);
+		MemoryContextSwitchTo(oldCxt);
+
+		/* reconnect spi */
+		SPI_restore_connection();
+	}
+	PG_END_TRY();
+}
+
+/*
+ * Initialize variable to NULL
+ */
+static void
+var_init_to_null(PLpgSQL_checkstate *cstate, int varno)
+{
+	PLpgSQL_var *var = (PLpgSQL_var *) cstate->estate->datums[varno];
+	var->value = (Datum) 0;
+	var->isnull = true;
+	var->freeval = false;
+}
+
+/*
+ * forward edata out from checker
+ */
+static void
+checker_error_edata(PLpgSQL_checkstate *cstate,
+					ErrorData *edata)
+{
+	checker_error(cstate,
+				 edata->sqlerrcode,
+				 edata->message,
+				 edata->detail,
+				 edata->hint,
+				 "error",
+				 edata->internalpos,
+				 edata->internalquery,
+				 edata->context);
+}
+
+/*
+ * Append text line (StringInfo) to tuple store.
+ */
+static void
+checker_store_string(PLpgSQL_checkstate *cstate, StringInfo str)
+{
+	Datum	value;
+	bool	isnull = false;
+	HeapTuple	tuple;
+
+	value = PointerGetDatum(cstring_to_text_with_len(str->data, str->len));
+	tuple = heap_form_tuple(cstate->tupdesc, &value, &isnull);
+
+	tuplestore_puttuple(cstate->tuple_store, tuple);
+
+	resetStringInfo(str);
+}
+
+/*
+ * prepare PLpgSQL_checkstate structure
+ */
+static void
+cstate_setup(PLpgSQL_checkstate *cstate,
+					   TupleDesc tupdesc,
+					   Tuplestorestate *tupstore,
+					   bool fatal_errors,
+					   int format)
+{
+	cstate->estate = NULL;
+	cstate->tupdesc = tupdesc;
+	cstate->tuple_store = tupstore;
+	cstate->fatal_errors = fatal_errors;
+	cstate->format = format;
+
+	if (format != PLPGSQL_CHECK_FORMAT_PLAIN)
+		cstate->sinfo = makeStringInfo();
+	else
+		cstate->sinfo = NULL;
+
+	/* put initial tag */
+	if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
+		appendStringInfoString(cstate->sinfo, "<Function>\n");
+}
+
+/*
+ * finishig a result stored in cstate
+ */
+static void
+cstate_flush(PLpgSQL_checkstate *cstate)
+{
+	if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
+		appendStringInfoString(cstate->sinfo, "</Function>");
+
+	if (cstate->format != PLPGSQL_CHECK_FORMAT_PLAIN)
+		checker_store_string(cstate, cstate->sinfo);
+}
+
+/*
+ * release check state
+ */
+static void
+destroy_cstate(PLpgSQL_checkstate *cstate)
+{
+	if (cstate->sinfo != NULL)
+	{
+		if (cstate->sinfo->data != NULL)
+			pfree(cstate->sinfo->data);
+		pfree(cstate->sinfo);
+
+		cstate->sinfo = NULL;
+	}
+}
+
+/*
+ * collects errors and warnings in plain text format
+ */
+static void
+checker_error_plain(PLpgSQL_checkstate *cstate,
+						 int sqlerrcode,
+						 const char *message,
+						 const char *detail,
+						 const char *hint,
+						 const char *level,
+						 int position,
+						 const char *query,
+						 const char *context)
+{
+	StringInfoData		sinfo;
+
+	initStringInfo(&sinfo);
+
+	Assert(message != NULL);
+	Assert(level != NULL);
+
+	if (cstate->estate && cstate->estate->err_stmt != NULL)
+		appendStringInfo(&sinfo, "%s:%s:%d:%s:%s",
+							    level, 
+							    unpack_sql_state(sqlerrcode),
+							    cstate->estate->err_stmt->lineno,
+							    plpgsql_stmt_typename(cstate->estate->err_stmt),
+							    message);
+	else
+		appendStringInfo(&sinfo, "%s:%s:%s",
+							    level, 
+							    unpack_sql_state(sqlerrcode),
+							    message);
+
+	checker_store_string(cstate, &sinfo);
+
+	if (query != NULL)
+	{
+		char   *query_line;					/* pointer to beginning of
+									 * current line */
+		int		line_caret_pos;
+		bool	is_first_line = true;
+		char	*_query = pstrdup(query);
+		char	*ptr;
+
+		ptr = _query;
+		query_line = ptr;
+		line_caret_pos = position;
+
+		while (*ptr != '\0')
+		{
+			/* search end of lines and replace '\n' by '\0' */
+			if (*ptr == '\n')
+			{
+				*ptr = '\0';
+				if (is_first_line)
+				{
+					appendStringInfo(&sinfo, "Query: %s", query_line);
+					is_first_line = false;
+				}
+				else
+					appendStringInfo(&sinfo, "       %s", query_line);
+
+				checker_store_string(cstate, &sinfo);
+
+				if (line_caret_pos > 0 && position == 0)
+				{
+					appendStringInfo(&sinfo, "--     %*s",
+									 line_caret_pos, "^");
+					checker_store_string(cstate, &sinfo);
+					line_caret_pos = 0;
+				}
+
+				/* store caret position offset for next line */
+				if (position > 1)
+					line_caret_pos = position - 1;
+
+				/* go to next line */
+				query_line = ptr + 1;
+			}
+
+			ptr += pg_mblen(ptr);
+
+			if (position > 0)
+				position--;
+		}
+
+		/* flush last line */
+		if (query_line != NULL)
+		{
+			if (is_first_line)
+				appendStringInfo(&sinfo, "Query: %s", query_line);
+			else
+				appendStringInfo(&sinfo, "       %s", query_line);
+
+			checker_store_string(cstate, &sinfo);
+
+			if (line_caret_pos > 0 && position == 0)
+			{
+				appendStringInfo(&sinfo, "--     %*s",
+								 line_caret_pos, "^");
+				checker_store_string(cstate, &sinfo);
+			}
+		}
+
+		pfree(_query);
+	}
+
+	if (detail != NULL)
+	{
+		appendStringInfo(&sinfo, "Detail: %s", detail);
+		checker_store_string(cstate, &sinfo);
+	}
+
+	if (hint != NULL)
+	{
+		appendStringInfo(&sinfo, "Hint: %s", hint);
+		checker_store_string(cstate, &sinfo);
+	}
+
+	if (context != NULL)
+	{
+		appendStringInfo(&sinfo, "Context: %s", context);
+		checker_store_string(cstate, &sinfo);
+	}
+
+	pfree(sinfo.data);
+}
+
+/*
+ * checker_error_xml formats and collects a identifided issues
+ */
+static void
+checker_error_xml(PLpgSQL_checkstate *cstate,
+						 int sqlerrcode,
+						 const char *message,
+						 const char *detail,
+						 const char *hint,
+						 const char *level,
+						 int position,
+						 const char *query,
+						 const char *context)
+{
+	Assert(message != NULL);
+	Assert(level != NULL);
+
+	/* there have to be prepared StringInfo for result */
+	Assert(cstate->sinfo != NULL);
+
+	/* flush tag */
+	appendStringInfoString(cstate->sinfo, "  <Issue>\n");
+
+	appendStringInfo(cstate->sinfo, "    <Level>%s</level>\n", level);
+	appendStringInfo(cstate->sinfo, "    <Sqlstate>%s</Sqlstate>\n",
+							 unpack_sql_state(sqlerrcode));
+	appendStringInfo(cstate->sinfo, "    <Message>%s</Message>\n",
+							    escape_xml(message));
+	if (cstate->estate->err_stmt != NULL)
+		appendStringInfo(cstate->sinfo, "    <Stmt lineno=\"%d\">%s</Stmt>\n",
+							 cstate->estate->err_stmt->lineno,
+							 plpgsql_stmt_typename(cstate->estate->err_stmt));
+	if (hint != NULL)
+		appendStringInfo(cstate->sinfo, "    <Hint>%s</Hint>\n",
+							    escape_xml(hint));
+	if (detail != NULL)
+		appendStringInfo(cstate->sinfo, "    <Detail>%s</Detail>\n",
+							    escape_xml(detail));
+	if(query != NULL)
+		appendStringInfo(cstate->sinfo, "    <Query position=\"%d\">%s</Query>\n",
+							    position, escape_xml(query));
+	if (context != NULL)
+		appendStringInfo(cstate->sinfo, "    <Context>%s</Context>\n",
+							    escape_xml(context));
+
+	/* flush closing tag */
+	appendStringInfoString(cstate->sinfo, "  </Issue>\n");
+}
+
+/*
+ * checker_error formats and collects a identifided issues
+ */
+static void
+checker_error(PLpgSQL_checkstate *cstate,
+						 int sqlerrcode,
+						 const char *message,
+						 const char *detail,
+						 const char *hint,
+						 const char *level,
+						 int position,
+						 const char *query,
+						 const char *context)
+{
+	if (cstate->format == PLPGSQL_CHECK_FORMAT_PLAIN)
+		checker_error_plain(cstate, sqlerrcode,
+						 message, detail, hint, level,
+						 position, query,
+						 context);
+	else if (cstate->format == PLPGSQL_CHECK_FORMAT_XML)
+		checker_error_xml(cstate, sqlerrcode,
+						 message, detail, hint, level,
+						 position, query,
+						 context);
+}
+
+/*
+ * Loads function's configuration
+ *
+ * Before checking function we have to load configuration related to function.
+ * This is function manager job, but we don't use it for checking.
+ */
+static int
+load_configuration(HeapTuple procTuple, bool *reload_config)
+{
+	Datum 	datum;
+	bool		isnull;
+	int		new_nest_level;
+
+	*reload_config = false;
+	new_nest_level = 0;
+
+	datum = SysCacheGetAttr(PROCOID, procTuple, Anum_pg_proc_proconfig, &isnull);
+	if (!isnull)
+	{
+		ArrayType	*set_items;
+
+		/* Set per-function configuration parameters */
+		set_items = DatumGetArrayTypeP(datum);
+
+		if (set_items != NULL)			/* Need a new GUC nesting level */
+		{
+			new_nest_level = NewGUCNestLevel();
+			*reload_config = true;
+			ProcessGUCArray(set_items,
+							(superuser() ? PGC_SUSET : PGC_USERSET),
+							PGC_S_SESSION,
+							GUC_ACTION_SAVE);
+		}
+	}
+
+	return new_nest_level;
+}
+
+/*
+ * Set up a fake fcinfo with just enough info to satisfy
+ * plpgsql_compile().
+ *
+ * there should be a different real argtypes for polymorphic params
+ */
+void
+plpgsql_setup_fake_fcinfo(FmgrInfo *flinfo,
+					FunctionCallInfoData *fcinfo,
+						    TriggerData *trigdata,
+								    Oid funcoid, bool istrigger)
+{
+	/* clean structures */
+	MemSet(fcinfo, 0, sizeof(FunctionCallInfoData));
+	MemSet(flinfo, 0, sizeof(FmgrInfo));
+
+	fcinfo->flinfo = flinfo;
+	flinfo->fn_oid = funcoid;
+	flinfo->fn_mcxt = CurrentMemoryContext;
+
+	if (istrigger)
+	{
+		Assert(trigdata != NULL);
+
+		MemSet(trigdata, 0, sizeof(TriggerData));
+		trigdata->type = T_TriggerData;
+		fcinfo->context = (Node *) trigdata;
+	}
+}
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index d43b8e0..2deb789 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -115,7 +115,6 @@ static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key)
 static void plpgsql_HashTableInsert(PLpgSQL_function *function,
 						PLpgSQL_func_hashkey *func_key);
 static void plpgsql_HashTableDelete(PLpgSQL_function *function);
-static void delete_function(PLpgSQL_function *func);
 
 /* ----------
  * plpgsql_compile		Make an execution tree for a PL/pgSQL function.
@@ -175,7 +174,7 @@ recheck:
 			 * Nope, so remove it from hashtable and try to drop associated
 			 * storage (if not done already).
 			 */
-			delete_function(function);
+			plpgsql_delete_function(function);
 
 			/*
 			 * If the function isn't in active use then we can overwrite the
@@ -2426,7 +2425,7 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 }
 
 /*
- * delete_function - clean up as much as possible of a stale function cache
+ * plpgsql_delete_function - clean up as much as possible of a stale function cache
  *
  * We can't release the PLpgSQL_function struct itself, because of the
  * possibility that there are fn_extra pointers to it.	We can release
@@ -2439,8 +2438,8 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
  * pointers to the same function cache.  Hence be careful not to do things
  * twice.
  */
-static void
-delete_function(PLpgSQL_function *func)
+void
+plpgsql_delete_function(PLpgSQL_function *func)
 {
 	/* remove function from hash table (might be done already) */
 	plpgsql_HashTableDelete(func);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a385b9a..4c22f81 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -80,7 +80,6 @@ static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
  * Local function forward declarations
  ************************************************************/
 static void plpgsql_exec_error_callback(void *arg);
-static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
 
 static int exec_stmt_block(PLpgSQL_execstate *estate,
 				PLpgSQL_stmt_block *block);
@@ -133,9 +132,6 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
 				  PLpgSQL_stmt_dynfors *stmt);
 
-static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
-					 PLpgSQL_function *func,
-					 ReturnSetInfo *rsi);
 static void exec_eval_cleanup(PLpgSQL_execstate *estate);
 
 static void exec_prepare_plan(PLpgSQL_execstate *estate,
@@ -181,10 +177,6 @@ static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
 static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
 				 PLpgSQL_expr *expr);
 static void plpgsql_param_fetch(ParamListInfo params, int paramid);
-static void exec_move_row(PLpgSQL_execstate *estate,
-			  PLpgSQL_rec *rec,
-			  PLpgSQL_row *row,
-			  HeapTuple tup, TupleDesc tupdesc);
 static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
 					PLpgSQL_row *row,
 					TupleDesc tupdesc);
@@ -204,7 +196,6 @@ static Datum exec_simple_cast_value(PLpgSQL_execstate *estate,
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
 static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
-static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
 static void free_var(PLpgSQL_var *var);
 static void assign_text_var(PLpgSQL_var *var, const char *str);
 static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
@@ -246,7 +237,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
 	 */
 	estate.err_text = gettext_noop("during initialization of execution state");
 	for (i = 0; i < estate.ndatums; i++)
-		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+		estate.datums[i] = plpgsql_copy_plpgsql_datum(func->datums[i]);
 
 	/*
 	 * Store the actual call argument values into the appropriate variables
@@ -290,15 +281,15 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
 						ItemPointerSetInvalid(&(tmptup.t_self));
 						tmptup.t_tableOid = InvalidOid;
 						tmptup.t_data = td;
-						exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
+						plpgsql_exec_move_row(&estate, NULL, row, &tmptup, tupdesc);
 						ReleaseTupleDesc(tupdesc);
 					}
 					else
 					{
 						/* If arg is null, treat it as an empty row */
-						exec_move_row(&estate, NULL, row, NULL, NULL);
+						plpgsql_exec_move_row(&estate, NULL, row, NULL, NULL);
 					}
-					/* clean up after exec_move_row() */
+					/* clean up after plpgsql_exec_move_row() */
 					exec_eval_cleanup(&estate);
 				}
 				break;
@@ -521,7 +512,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 	 */
 	estate.err_text = gettext_noop("during initialization of execution state");
 	for (i = 0; i < estate.ndatums; i++)
-		estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+		estate.datums[i] = plpgsql_copy_plpgsql_datum(func->datums[i]);
 
 	/*
 	 * Put the OLD and NEW tuples into record variables
@@ -839,8 +830,8 @@ plpgsql_exec_error_callback(void *arg)
  * Support function for initializing local execution variables
  * ----------
  */
-static PLpgSQL_datum *
-copy_plpgsql_datum(PLpgSQL_datum *datum)
+PLpgSQL_datum *
+plpgsql_copy_plpgsql_datum(PLpgSQL_datum *datum)
 {
 	PLpgSQL_datum *result;
 
@@ -2857,7 +2848,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
  * Initialize a mostly empty execution state
  * ----------
  */
-static void
+void
 plpgsql_estate_setup(PLpgSQL_execstate *estate,
 					 PLpgSQL_function *func,
 					 ReturnSetInfo *rsi)
@@ -3173,7 +3164,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 						(errcode(ERRCODE_NO_DATA_FOUND),
 						 errmsg("query returned no rows")));
 			/* set the target to NULL(s) */
-			exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
 		}
 		else
 		{
@@ -3182,7 +3173,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 						(errcode(ERRCODE_TOO_MANY_ROWS),
 						 errmsg("query returned more than one row")));
 			/* Put the first result row into the target */
-			exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
 		}
 
 		/* Clean up */
@@ -3347,7 +3338,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 						(errcode(ERRCODE_NO_DATA_FOUND),
 						 errmsg("query returned no rows")));
 			/* set the target to NULL(s) */
-			exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
 		}
 		else
 		{
@@ -3356,9 +3347,9 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 						(errcode(ERRCODE_TOO_MANY_ROWS),
 						 errmsg("query returned more than one row")));
 			/* Put the first result row into the target */
-			exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
 		}
-		/* clean up after exec_move_row() */
+		/* clean up after plpgsql_exec_move_row() */
 		exec_eval_cleanup(estate);
 	}
 	else
@@ -3629,9 +3620,9 @@ exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
 		 * ----------
 		 */
 		if (n == 0)
-			exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
 		else
-			exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
 
 		exec_eval_cleanup(estate);
 		SPI_freetuptable(tuptab);
@@ -3804,7 +3795,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 				if (*isNull)
 				{
 					/* If source is null, just assign nulls to the row */
-					exec_move_row(estate, NULL, row, NULL, NULL);
+					plpgsql_exec_move_row(estate, NULL, row, NULL, NULL);
 				}
 				else
 				{
@@ -3830,7 +3821,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 					ItemPointerSetInvalid(&(tmptup.t_self));
 					tmptup.t_tableOid = InvalidOid;
 					tmptup.t_data = td;
-					exec_move_row(estate, NULL, row, &tmptup, tupdesc);
+					plpgsql_exec_move_row(estate, NULL, row, &tmptup, tupdesc);
 					ReleaseTupleDesc(tupdesc);
 				}
 				break;
@@ -3846,7 +3837,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 				if (*isNull)
 				{
 					/* If source is null, just assign nulls to the record */
-					exec_move_row(estate, rec, NULL, NULL, NULL);
+					plpgsql_exec_move_row(estate, rec, NULL, NULL, NULL);
 				}
 				else
 				{
@@ -3873,7 +3864,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
 					ItemPointerSetInvalid(&(tmptup.t_self));
 					tmptup.t_tableOid = InvalidOid;
 					tmptup.t_data = td;
-					exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
+					plpgsql_exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
 					ReleaseTupleDesc(tupdesc);
 				}
 				break;
@@ -4712,7 +4703,7 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
 	 */
 	if (n <= 0)
 	{
-		exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
+		plpgsql_exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
 		exec_eval_cleanup(estate);
 	}
 	else
@@ -4730,7 +4721,7 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
 			/*
 			 * Assign the tuple to the target
 			 */
-			exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+			plpgsql_exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
 			exec_eval_cleanup(estate);
 
 			/*
@@ -5122,14 +5113,14 @@ plpgsql_param_fetch(ParamListInfo params, int paramid)
 
 
 /* ----------
- * exec_move_row			Move one tuple's values into a record or row
+ * plpgsql_exec_move_row			Move one tuple's values into a record or row
  *
  * Since this uses exec_assign_value, caller should eventually call
  * exec_eval_cleanup to prevent long-term memory leaks.
  * ----------
  */
-static void
-exec_move_row(PLpgSQL_execstate *estate,
+void
+plpgsql_exec_move_row(PLpgSQL_execstate *estate,
 			  PLpgSQL_rec *rec,
 			  PLpgSQL_row *row,
 			  HeapTuple tup, TupleDesc tupdesc)
@@ -5923,7 +5914,7 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
  * We check that it matches the top stack entry, and destroy the stack
  * entry along with the context.
  */
-static void
+void
 plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
 {
 	SimpleEcontextStackEntry *next;
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 022ec3f..4d3364c 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -15,6 +15,8 @@
 
 #include "plpgsql.h"
 
+#include "catalog/pg_enum.h"
+#include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
@@ -39,6 +41,8 @@ int			plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
 /* Hook for plugins */
 PLpgSQL_plugin **plugin_ptr = NULL;
 
+static void precheck_function(HeapTuple procTuple, bool has_trigger_relation);
+static int check_function_output_format(Oid format);
 
 /*
  * _PG_init()			- library load-time initialization
@@ -180,11 +184,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
 	 * plpgsql_exec_function().  In particular note that this sets things up
 	 * with no arguments passed.
 	 */
-	MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
-	MemSet(&flinfo, 0, sizeof(flinfo));
-	fake_fcinfo.flinfo = &flinfo;
-	flinfo.fn_oid = InvalidOid;
-	flinfo.fn_mcxt = CurrentMemoryContext;
+	plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, NULL, InvalidOid, false);
 
 	retval = plpgsql_exec_function(func, &fake_fcinfo);
 
@@ -286,17 +286,7 @@ plpgsql_validator(PG_FUNCTION_ARGS)
 		 * Set up a fake fcinfo with just enough info to satisfy
 		 * plpgsql_compile().
 		 */
-		MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
-		MemSet(&flinfo, 0, sizeof(flinfo));
-		fake_fcinfo.flinfo = &flinfo;
-		flinfo.fn_oid = funcoid;
-		flinfo.fn_mcxt = CurrentMemoryContext;
-		if (istrigger)
-		{
-			MemSet(&trigdata, 0, sizeof(trigdata));
-			trigdata.type = T_TriggerData;
-			fake_fcinfo.context = (Node *) &trigdata;
-		}
+		plpgsql_setup_fake_fcinfo(&flinfo, &fake_fcinfo, &trigdata, funcoid, istrigger);
 
 		/* Test-compile the function */
 		plpgsql_compile(&fake_fcinfo, true);
@@ -312,3 +302,153 @@ plpgsql_validator(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
+/*
+ * ----------
+ * plpgsql_check_function
+ *
+ * It ensure a detailed validation
+ * ----------
+ */
+PG_FUNCTION_INFO_V1(plpgsql_check_function);
+
+Datum
+plpgsql_check_function(PG_FUNCTION_ARGS)
+{
+	Oid			funcoid = PG_GETARG_OID(0);
+	Oid			relid = PG_GETARG_OID(1);
+	bool			fatal_errors = PG_GETARG_BOOL(2);
+	Oid			format_oid = PG_GETARG_OID(3);
+	TupleDesc       tupdesc;
+	HeapTuple	procTuple;
+	Tuplestorestate *tupstore;
+	MemoryContext per_query_ctx;
+	MemoryContext oldcontext;
+	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	int		format = PLPGSQL_CHECK_FORMAT_PLAIN;
+
+	/* check to see if caller supports us returning a tuplestore */
+	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("set-valued function called in context that cannot accept a set")));
+
+	if (!(rsinfo->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	procTuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+	if (!HeapTupleIsValid(procTuple))
+		elog(ERROR, "cache lookup failed for function %u", funcoid);
+
+	precheck_function(procTuple, OidIsValid(relid));
+	format = check_function_output_format(format_oid);
+
+	/* need to build tuplestore in query context */
+	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+	oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+	tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
+	tupstore = tuplestore_begin_heap(false, false, work_mem);
+	MemoryContextSwitchTo(oldcontext);
+
+	plpgsql_function_check(procTuple, relid,
+						tupdesc, tupstore,
+								    fatal_errors, format);
+
+	ReleaseSysCache(procTuple);
+
+	/* clean up and return the tuplestore */
+	tuplestore_donestoring(tupstore);
+
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+
+	return (Datum) 0;
+}
+
+/*
+ * Process necessary checking before code checking
+ */
+static void
+precheck_function(HeapTuple procTuple, bool has_trigger_relation)
+{
+	Form_pg_proc		proc;
+	Form_pg_language	languageStruct;
+	HeapTuple	languageTuple;
+	char	   functyptype;
+	char	   *funcname;
+	bool		is_trigger = false;
+
+	proc = (Form_pg_proc) GETSTRUCT(procTuple);
+
+	funcname = format_procedure(HeapTupleGetOid(procTuple));
+
+	/* used language must be plpgsql */
+	languageTuple = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
+	Assert(HeapTupleIsValid(languageTuple));
+
+	languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
+	if (strcmp(NameStr(languageStruct->lanname), "plpgsql") != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("%s is not a plpgsql function", funcname)));
+
+	ReleaseSysCache(languageTuple);
+
+	functyptype = get_typtype(proc->prorettype);
+
+	if (functyptype == TYPTYPE_PSEUDO)
+	{
+		/* we assume OPAQUE with no arguments means a trigger */
+		if (proc->prorettype == TRIGGEROID ||
+			(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
+		{
+			is_trigger = true;
+			if (!has_trigger_relation)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						 errmsg("missing trigger relation"),
+						 errhint("Trigger relation oid must be valid")));
+		}
+	}
+
+	if (has_trigger_relation && !is_trigger)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("function is not trigger"),
+				 errhint("Trigger relation oid must not be valid for non trigger function.")));
+
+	pfree(funcname);
+}
+
+/*
+ * Returns output format for plpgsql_check_function
+ */
+static int
+check_function_output_format(Oid format)
+{
+	HeapTuple	tuple;
+	char		*label;
+	int	result = -1;
+
+	tuple = SearchSysCache1(ENUMOID, ObjectIdGetDatum(format));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+				 errmsg("invalid internal value for enum: %u",
+						format)));
+
+	label = NameStr(((Form_pg_enum) GETSTRUCT(tuple))->enumlabel);
+
+	if (strcmp(label, "XML") == 0)
+		result = PLPGSQL_CHECK_FORMAT_XML;
+	else if (strcmp(label, "PLAIN_TEXT") == 0)
+		result = PLPGSQL_CHECK_FORMAT_PLAIN;
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
deleted file mode 100644
index 5eeea56..0000000
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ /dev/null
@@ -1,11 +0,0 @@
-/* src/pl/plpgsql/src/plpgsql--1.0.sql */
-
-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
-
-CREATE PROCEDURAL LANGUAGE plpgsql;
-
-COMMENT ON PROCEDURAL LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql--1.1.sql b/src/pl/plpgsql/src/plpgsql--1.1.sql
new file mode 100644
index 0000000..20f43f7
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql--1.1.sql
@@ -0,0 +1,21 @@
+/* src/pl/plpgsql/src/plpgsql--1.1.sql */
+
+/*
+ * Currently, all the interesting stuff is done by CREATE LANGUAGE.
+ * Later we will probably "dumb down" that command and put more of the
+ * knowledge into this script.
+ */
+
+CREATE PROCEDURAL LANGUAGE plpgsql;
+
+COMMENT ON PROCEDURAL LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
+
+CREATE TYPE plpgsql_check_function_format AS ENUM ('XML', 'PLAIN_TEXT');
+
+CREATE FUNCTION plpgsql_check_function(funcoid regprocedure,
+					relid regclass = 0,
+					fatal_errors boolean = true,
+					format plpgsql_check_function_format = 'PLAIN_TEXT')
+	RETURNS SETOF text AS 'MODULE_PATHNAME'
+	LANGUAGE C
+	RETURNS NULL ON NULL INPUT;
diff --git a/src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql b/src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql
deleted file mode 100644
index 9de7e83..0000000
--- a/src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql
+++ /dev/null
@@ -1,7 +0,0 @@
-/* src/pl/plpgsql/src/plpgsql--unpackaged--1.0.sql */
-
-ALTER EXTENSION plpgsql ADD PROCEDURAL LANGUAGE plpgsql;
--- ALTER ADD LANGUAGE doesn't pick up the support functions, so we have to.
-ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
-ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
-ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
diff --git a/src/pl/plpgsql/src/plpgsql--unpackaged--1.1.sql b/src/pl/plpgsql/src/plpgsql--unpackaged--1.1.sql
new file mode 100644
index 0000000..30e1a5f
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql--unpackaged--1.1.sql
@@ -0,0 +1,9 @@
+/* src/pl/plpgsql/src/plpgsql--unpackaged--1.1.sql */
+
+ALTER EXTENSION plpgsql ADD PROCEDURAL LANGUAGE plpgsql;
+-- ALTER ADD LANGUAGE doesn't pick up the support functions, so we have to.
+ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_call_handler();
+ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_inline_handler(internal);
+ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_validator(oid);
+ALTER EXTENSION plpgsql ADD FUNCTION plpgsql_check_function(regprocedure, regclass, boolean, plpgsql_check_function_format);
+ALTER EXTENSION plpgsql ADD TYPE plpgsql_check_function_format;
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..4c75c93 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -1,6 +1,6 @@
 # plpgsql extension
 comment = 'PL/pgSQL procedural language'
-default_version = '1.0'
+default_version = '1.1'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b63f336..59a93eb 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -767,6 +767,20 @@ typedef struct PLpgSQL_execstate
 	void	   *plugin_info;	/* reserved for use by optional plugin */
 } PLpgSQL_execstate;
 
+enum {
+	PLPGSQL_CHECK_FORMAT_PLAIN,
+	PLPGSQL_CHECK_FORMAT_XML
+};
+
+typedef struct PLpgSQL_checkstate
+{
+	PLpgSQL_execstate   *estate;			/* check state is estate extension */
+	Tuplestorestate *tuple_store;
+	TupleDesc	tupdesc;
+	StringInfo	sinfo;
+	bool			fatal_errors;		/* stop on first error */
+	int			format;
+} PLpgSQL_checkstate;
 
 /*
  * A PLpgSQL_plugin structure represents an instrumentation plugin.
@@ -902,6 +916,7 @@ extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
 extern void plpgsql_adddatum(PLpgSQL_datum *new);
 extern int	plpgsql_add_initdatums(int **varnos);
 extern void plpgsql_HashTableInit(void);
+extern void plpgsql_delete_function(PLpgSQL_function *func);
 
 /* ----------
  * Functions in pl_handler.c
@@ -911,6 +926,7 @@ extern void _PG_init(void);
 extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS);
 extern Datum plpgsql_inline_handler(PG_FUNCTION_ARGS);
 extern Datum plpgsql_validator(PG_FUNCTION_ARGS);
+extern Datum plpgsql_check_function(PG_FUNCTION_ARGS);
 
 /* ----------
  * Functions in pl_exec.c
@@ -928,6 +944,26 @@ extern Oid exec_get_datum_type(PLpgSQL_execstate *estate,
 extern void exec_get_datum_type_info(PLpgSQL_execstate *estate,
 						 PLpgSQL_datum *datum,
 						 Oid *typeid, int32 *typmod, Oid *collation);
+extern PLpgSQL_datum *plpgsql_copy_plpgsql_datum(PLpgSQL_datum *datum);
+extern void plpgsql_estate_setup(PLpgSQL_execstate *estate,
+					 PLpgSQL_function *func,
+					 ReturnSetInfo *rsi);
+extern void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
+extern void plpgsql_exec_move_row(PLpgSQL_execstate *estate,
+			  PLpgSQL_rec *rec,
+			  PLpgSQL_row *row,
+			  HeapTuple tup, TupleDesc tupdesc);
+
+/* ----------
+ * Functions for namespace handling in pl_check.c
+ * ----------
+ */
+extern void plpgsql_function_check(HeapTuple procTuple, Oid relid,
+					    TupleDesc tupdesc,
+					    Tuplestorestate *tupstore,
+							    bool fatal_errors, int format);
+extern void plpgsql_setup_fake_fcinfo(FmgrInfo *flinfo, FunctionCallInfoData *fcinfo,
+					TriggerData *trigdata, Oid funcoid, bool istrigger);
 
 /* ----------
  * Functions for namespace handling in pl_funcs.c
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 5455ade..0d9d7d7 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -302,6 +302,12 @@ end;
 ' language plpgsql;
 create trigger tg_hslot_biu before insert or update
     on HSlot for each row execute procedure tg_hslot_biu();
+-- check trigger should not fail
+select plpgsql_check_function('tg_hslot_biu()', 'HSlot');
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
 -- ************************************************************
 -- * BEFORE DELETE on HSlot
 -- *	- prevent from manual manipulation
@@ -635,6 +641,12 @@ begin
     raise exception ''illegal backlink beginning with %'', mytype;
 end;
 ' language plpgsql;
+-- check function should not fail
+select plpgsql_check_function('tg_backlink_set(bpchar, bpchar)', 0);
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
 -- ************************************************************
 -- * Support function to clear out the backlink field if
 -- * it still points to specific slot
@@ -2931,6 +2943,38 @@ NOTICE:  4 bb cc
  
 (1 row)
 
+-- check function should not fail
+select plpgsql_check_function('for_vect()');
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
+-- recheck after check function
+select for_vect();
+NOTICE:  1
+NOTICE:  2
+NOTICE:  3
+NOTICE:  1 BB CC
+NOTICE:  2 BB CC
+NOTICE:  3 BB CC
+NOTICE:  4 BB CC
+NOTICE:  1
+NOTICE:  2
+NOTICE:  3
+NOTICE:  4
+NOTICE:  1 BB CC
+NOTICE:  2 BB CC
+NOTICE:  3 BB CC
+NOTICE:  4 BB CC
+NOTICE:  1 bb cc
+NOTICE:  2 bb cc
+NOTICE:  3 bb cc
+NOTICE:  4 bb cc
+ for_vect 
+----------
+ 
+(1 row)
+
 -- regression test: verify that multiple uses of same plpgsql datum within
 -- a SQL command all get mapped to the same $n parameter.  The return value
 -- of the SELECT is not important, we only care that it doesn't fail with
@@ -3412,6 +3456,12 @@ begin
   return;
 end;
 $$ language plpgsql;
+-- check function should not fail
+select plpgsql_check_function('forc01()');
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
 select forc01();
 NOTICE:  5 from c
 NOTICE:  6 from c
@@ -3845,6 +3895,12 @@ begin
   end case;
 end;
 $$ language plpgsql immutable;
+-- check function should not fail
+select plpgsql_check_function('case_test(bigint)');
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
 select case_test(1);
  case_test 
 -----------
@@ -4700,3 +4756,528 @@ ERROR:  value for domain orderedarray violates check constraint "sorted"
 CONTEXT:  PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
 drop function arrayassign1();
 drop function testoa(x1 int, x2 int, x3 int);
+--
+-- check function statement tests
+--
+--should fail - is not plpgsql
+select plpgsql_check_function('session_user()');
+ERROR:  "session_user"() is not a plpgsql function
+create table t1(a int, b int);
+create function f1()
+returns void as $$
+begin
+  if false then
+    update t1 set c = 30;
+  end if;
+  if false then 
+    raise notice '% %', r.c;
+  end if;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()', fatal_errors := true);
+                         plpgsql_check_function                         
+------------------------------------------------------------------------
+ error:42703:4:SQL statement:column "c" of relation "t1" does not exist
+ Query: update t1 set c = 30
+ --                   ^
+(3 rows)
+
+select plpgsql_check_function('f1()', fatal_errors := false);
+                         plpgsql_check_function                         
+------------------------------------------------------------------------
+ error:42703:4:SQL statement:column "c" of relation "t1" does not exist
+ Query: update t1 set c = 30
+ --                   ^
+ error:42P01:7:RAISE:missing FROM-clause entry for table "r"
+ Query: SELECT r.c
+ --            ^
+ error:42601:7:RAISE:too few parameters specified for RAISE
+(7 rows)
+
+select plpgsql_check_function('f1()');
+                         plpgsql_check_function                         
+------------------------------------------------------------------------
+ error:42703:4:SQL statement:column "c" of relation "t1" does not exist
+ Query: update t1 set c = 30
+ --                   ^
+(3 rows)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+create function g1(out a int, out b int)
+as $$
+  select 10,20;
+$$ language sql;
+create function f1()
+returns void as $$
+declare r record;
+begin
+  r := g1();
+  if false then 
+    raise notice '%', r.c;
+  end if;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+             plpgsql_check_function              
+-------------------------------------------------
+ error:42703:6:RAISE:record "r" has no field "c"
+ Context: SQL statement "SELECT r.c"
+(2 rows)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+drop function g1();
+create function g1(out a int, out b int)
+returns setof record as $$
+select * from t1;
+$$ language sql;
+create function f1()
+returns void as $$
+declare r record;
+begin
+  for r in select * from g1()
+  loop
+    raise notice '%', r.c;
+  end loop;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+             plpgsql_check_function              
+-------------------------------------------------
+ error:42703:6:RAISE:record "r" has no field "c"
+ Context: SQL statement "SELECT r.c"
+(2 rows)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+create or replace function f1()
+returns void as $$
+declare r record;
+begin
+  for r in select * from g1()
+  loop
+    r.c := 20;
+  end loop;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+                plpgsql_check_function                
+------------------------------------------------------
+ error:42703:6:assignment:record "r" has no field "c"
+(1 row)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+drop function g1();
+create function f1()
+returns int as $$
+declare r int;
+begin
+  if false then
+    r := a + b;
+  end if;
+  return r;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+   
+(1 row)
+
+select plpgsql_check_function('f1()');
+               plpgsql_check_function               
+----------------------------------------------------
+ error:42703:5:assignment:column "a" does not exist
+ Query: SELECT a + b
+ --            ^
+(3 rows)
+
+select f1();
+ f1 
+----
+   
+(1 row)
+
+drop function f1();
+create or replace function f1()
+returns void as $$
+begin
+  if false then
+    raise notice '%', 1, 2;
+  end if;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+                   plpgsql_check_function                    
+-------------------------------------------------------------
+ error:42601:4:RAISE:too many parameters specified for RAISE
+(1 row)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+create or replace function f1()
+returns void as $$
+begin
+  if false then
+    raise notice '% %';
+  end if;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+                   plpgsql_check_function                   
+------------------------------------------------------------
+ error:42601:4:RAISE:too few parameters specified for RAISE
+(1 row)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+create or replace function f1()
+returns void as $$
+declare r int[];
+begin
+  if false then
+    r[c+10] := 20;
+  end if;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+               plpgsql_check_function               
+----------------------------------------------------
+ error:42703:5:assignment:column "c" does not exist
+ Query: SELECT c+10
+ --            ^
+(3 rows)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+create or replace function f1()
+returns void as $$
+declare r int;
+begin
+  if false then
+    r[10] := 20;
+  end if;
+end;
+$$ language plpgsql set search_path = public;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+                   plpgsql_check_function                    
+-------------------------------------------------------------
+ error:42804:5:assignment:subscripted object is not an array
+(1 row)
+
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+drop function f1();
+create type _exception_type as (
+  state text,
+  message text,
+  detail text);
+create or replace function f1()
+returns void as $$
+declare
+  _exception record;
+begin
+  _exception := NULL::_exception_type;
+exception when others then
+  get stacked diagnostics
+        _exception.state = RETURNED_SQLSTATE,
+        _exception.message = MESSAGE_TEXT,
+        _exception.detail = PG_EXCEPTION_DETAIL,
+        _exception.hint = PG_EXCEPTION_HINT;
+end;
+$$ language plpgsql;
+select f1();
+ f1 
+----
+ 
+(1 row)
+
+select plpgsql_check_function('f1()');
+                        plpgsql_check_function                         
+-----------------------------------------------------------------------
+ error:42703:7:GET DIAGNOSTICS:record "_exception" has no field "hint"
+(1 row)
+
+drop function f1();
+create or replace function f1_trg()
+returns trigger as $$
+begin
+  if new.a > 10 then
+    raise notice '%', new.b;
+    raise notice '%', new.c;
+  end if;
+  return new;
+end;
+$$ language plpgsql;
+create trigger t1_f1 before insert on t1
+  for each row
+  execute procedure f1_trg();
+insert into t1 values(6,30);
+select plpgsql_check_function('f1_trg()','t1');
+              plpgsql_check_function               
+---------------------------------------------------
+ error:42703:5:RAISE:record "new" has no field "c"
+ Context: SQL statement "SELECT new.c"
+(2 rows)
+
+insert into t1 values(6,30);
+create or replace function f1_trg()
+returns trigger as $$
+begin
+  new.a := new.a + 10;
+  new.b := new.b + 10;
+  new.c := 30;
+  return new;
+end;
+$$ language plpgsql;
+-- should to fail
+select plpgsql_check_function('f1_trg()','t1');
+                 plpgsql_check_function                 
+--------------------------------------------------------
+ error:42703:5:assignment:record "new" has no field "c"
+(1 row)
+
+-- should to fail but not crash
+insert into t1 values(6,30);
+ERROR:  record "new" has no field "c"
+CONTEXT:  PL/pgSQL function f1_trg() line 5 at assignment
+create or replace function f1_trg()
+returns trigger as $$
+begin
+  new.a := new.a + 10;
+  new.b := new.b + 10;
+  return new;
+end;
+$$ language plpgsql;
+-- ok
+select plpgsql_check_function('f1_trg()', 't1');
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
+-- ok
+insert into t1 values(6,30);
+select * from t1;
+ a  | b  
+----+----
+  6 | 30
+  6 | 30
+ 16 | 40
+(3 rows)
+
+drop trigger t1_f1 on t1;
+drop function f1_trg();
+-- test of showing caret on correct place for multiline queries
+create or replace function f1()
+returns void as $$
+begin
+  select
+  var
+  from
+  foo;
+end;
+$$ language plpgsql;
+select plpgsql_check_function('f1()');
+                 plpgsql_check_function                  
+---------------------------------------------------------
+ error:42703:3:SQL statement:column "var" does not exist
+ Query: select
+          var
+ --       ^
+          from
+          foo
+(6 rows)
+
+drop function f1();
+create or replace function f1()
+returns int as $$
+begin
+  return (select a
+             from t1
+            where hh = 20);
+end;
+$$ language plpgsql;
+select plpgsql_check_function('f1()');
+             plpgsql_check_function              
+-------------------------------------------------
+ error:42703:3:RETURN:column "hh" does not exist
+ Query: SELECT (select a
+                     from t1
+                    where hh = 20)
+ --                       ^
+(5 rows)
+
+create or replace function f1()
+returns int as $$
+begin
+  return (select a
+             from txxxxxxx
+            where hh = 20);
+end;
+$$ language plpgsql;
+select plpgsql_check_function('f1()');
+                 plpgsql_check_function                  
+---------------------------------------------------------
+ error:42P01:3:RETURN:relation "txxxxxxx" does not exist
+ Query: SELECT (select a
+                     from txxxxxxx
+ --                       ^
+                    where hh = 20)
+(5 rows)
+
+drop function f1();
+drop table t1;
+drop type _exception_type;
+-- raise warnings when target row has different number of attributies in
+-- SELECT INTO statement
+create or replace function f1()
+returns void as $$
+declare a1 int; a2 int;
+begin
+  select 10,20 into a1,a2;
+end;
+$$ language plpgsql;
+-- should be ok
+select plpgsql_check_function('f1()');
+ plpgsql_check_function 
+------------------------
+(0 rows)
+
+create or replace function f1()
+returns void as $$
+declare a1 int; a2 int;
+begin
+  select 10,20 into a1;
+end;
+$$ language plpgsql;
+-- raise warning
+select plpgsql_check_function('f1()');
+                         plpgsql_check_function                          
+-------------------------------------------------------------------------
+ warning:00000:4:SQL statement:too many attributies for target variables
+ Detail: There are less target variables than output columns in query.
+ Hint: Check target variables in SELECT INTO statement
+(3 rows)
+
+create or replace function f1()
+returns void as $$
+declare a1 int; a2 int;
+begin
+  select 10 into a1,a2;
+end;
+$$ language plpgsql;
+-- raise warning
+select plpgsql_check_function('f1()');
+                         plpgsql_check_function                         
+------------------------------------------------------------------------
+ warning:00000:4:SQL statement:too few attributies for target variables
+ Detail: There are more target variables than output columns in query.
+ Hint: Check target variables in SELECT INTO statement.
+(3 rows)
+
+-- bogus code
+set check_function_bodies to off;
+create or replace function f1()
+returns void as $$ 
+adasdfsadf
+$$ language plpgsql;
+select plpgsql_check_function('f1()');
+                   plpgsql_check_function                   
+------------------------------------------------------------
+ error:42601:syntax error at or near "adasdfsadf"
+ Query:  
+        adasdfsadf
+ --     ^
+        
+ Context: compilation of PL/pgSQL function "f1" near line 1
+(6 rows)
+
+drop function f1();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index f577dc3..5245968 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -366,6 +366,8 @@ end;
 create trigger tg_hslot_biu before insert or update
     on HSlot for each row execute procedure tg_hslot_biu();
 
+-- check trigger should not fail
+select plpgsql_check_function('tg_hslot_biu()', 'HSlot');
 
 -- ************************************************************
 -- * BEFORE DELETE on HSlot
@@ -747,6 +749,9 @@ begin
 end;
 ' language plpgsql;
 
+-- check function should not fail
+select plpgsql_check_function('tg_backlink_set(bpchar, bpchar)', 0);
+
 
 -- ************************************************************
 -- * Support function to clear out the backlink field if
@@ -2443,6 +2448,13 @@ $proc$ language plpgsql;
 
 select for_vect();
 
+-- check function should not fail
+select plpgsql_check_function('for_vect()');
+
+-- recheck after check function
+select for_vect();
+
+
 -- regression test: verify that multiple uses of same plpgsql datum within
 -- a SQL command all get mapped to the same $n parameter.  The return value
 -- of the SELECT is not important, we only care that it doesn't fail with
@@ -2822,6 +2834,9 @@ begin
 end;
 $$ language plpgsql;
 
+-- check function should not fail
+select plpgsql_check_function('forc01()');
+
 select forc01();
 
 -- try updating the cursor's current row
@@ -3156,6 +3171,10 @@ begin
 end;
 $$ language plpgsql immutable;
 
+-- check function should not fail
+select plpgsql_check_function('case_test(bigint)');
+
+
 select case_test(1);
 select case_test(2);
 select case_test(3);
@@ -3708,3 +3727,365 @@ select testoa(1,2,1); -- fail at update
 
 drop function arrayassign1();
 drop function testoa(x1 int, x2 int, x3 int);
+
+--
+-- check function statement tests
+--
+
+--should fail - is not plpgsql
+select plpgsql_check_function('session_user()');
+
+create table t1(a int, b int);
+
+create function f1()
+returns void as $$
+begin
+  if false then
+    update t1 set c = 30;
+  end if;
+  if false then 
+    raise notice '% %', r.c;
+  end if;
+end;
+$$ language plpgsql;
+
+select f1();
+select plpgsql_check_function('f1()', fatal_errors := true);
+select plpgsql_check_function('f1()', fatal_errors := false);
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+
+create function g1(out a int, out b int)
+as $$
+  select 10,20;
+$$ language sql;
+
+create function f1()
+returns void as $$
+declare r record;
+begin
+  r := g1();
+  if false then 
+    raise notice '%', r.c;
+  end if;
+end;
+$$ language plpgsql;
+
+select f1();
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+drop function g1();
+
+create function g1(out a int, out b int)
+returns setof record as $$
+select * from t1;
+$$ language sql;
+
+create function f1()
+returns void as $$
+declare r record;
+begin
+  for r in select * from g1()
+  loop
+    raise notice '%', r.c;
+  end loop;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+create or replace function f1()
+returns void as $$
+declare r record;
+begin
+  for r in select * from g1()
+  loop
+    r.c := 20;
+  end loop;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+drop function g1();
+
+create function f1()
+returns int as $$
+declare r int;
+begin
+  if false then
+    r := a + b;
+  end if;
+  return r;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+
+create or replace function f1()
+returns void as $$
+begin
+  if false then
+    raise notice '%', 1, 2;
+  end if;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+
+create or replace function f1()
+returns void as $$
+begin
+  if false then
+    raise notice '% %';
+  end if;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+
+create or replace function f1()
+returns void as $$
+declare r int[];
+begin
+  if false then
+    r[c+10] := 20;
+  end if;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+
+create or replace function f1()
+returns void as $$
+declare r int;
+begin
+  if false then
+    r[10] := 20;
+  end if;
+end;
+$$ language plpgsql set search_path = public;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+select f1();
+
+drop function f1();
+
+create type _exception_type as (
+  state text,
+  message text,
+  detail text);
+
+create or replace function f1()
+returns void as $$
+declare
+  _exception record;
+begin
+  _exception := NULL::_exception_type;
+exception when others then
+  get stacked diagnostics
+        _exception.state = RETURNED_SQLSTATE,
+        _exception.message = MESSAGE_TEXT,
+        _exception.detail = PG_EXCEPTION_DETAIL,
+        _exception.hint = PG_EXCEPTION_HINT;
+end;
+$$ language plpgsql;
+
+select f1();
+
+select plpgsql_check_function('f1()');
+
+drop function f1();
+
+create or replace function f1_trg()
+returns trigger as $$
+begin
+  if new.a > 10 then
+    raise notice '%', new.b;
+    raise notice '%', new.c;
+  end if;
+  return new;
+end;
+$$ language plpgsql;
+
+create trigger t1_f1 before insert on t1
+  for each row
+  execute procedure f1_trg();
+
+insert into t1 values(6,30);
+
+select plpgsql_check_function('f1_trg()','t1');
+
+insert into t1 values(6,30);
+
+create or replace function f1_trg()
+returns trigger as $$
+begin
+  new.a := new.a + 10;
+  new.b := new.b + 10;
+  new.c := 30;
+  return new;
+end;
+$$ language plpgsql;
+
+-- should to fail
+
+select plpgsql_check_function('f1_trg()','t1');
+
+-- should to fail but not crash
+insert into t1 values(6,30);
+
+create or replace function f1_trg()
+returns trigger as $$
+begin
+  new.a := new.a + 10;
+  new.b := new.b + 10;
+  return new;
+end;
+$$ language plpgsql;
+
+-- ok
+select plpgsql_check_function('f1_trg()', 't1');
+
+-- ok
+insert into t1 values(6,30);
+
+select * from t1;
+
+drop trigger t1_f1 on t1;
+
+drop function f1_trg();
+
+-- test of showing caret on correct place for multiline queries
+create or replace function f1()
+returns void as $$
+begin
+  select
+  var
+  from
+  foo;
+end;
+$$ language plpgsql;
+
+select plpgsql_check_function('f1()');
+
+drop function f1();
+
+create or replace function f1()
+returns int as $$
+begin
+  return (select a
+             from t1
+            where hh = 20);
+end;
+$$ language plpgsql;
+
+select plpgsql_check_function('f1()');
+
+create or replace function f1()
+returns int as $$
+begin
+  return (select a
+             from txxxxxxx
+            where hh = 20);
+end;
+$$ language plpgsql;
+
+select plpgsql_check_function('f1()');
+
+drop function f1();
+
+drop table t1;
+drop type _exception_type;
+
+-- raise warnings when target row has different number of attributies in
+-- SELECT INTO statement
+
+create or replace function f1()
+returns void as $$
+declare a1 int; a2 int;
+begin
+  select 10,20 into a1,a2;
+end;
+$$ language plpgsql;
+
+-- should be ok
+select plpgsql_check_function('f1()');
+
+create or replace function f1()
+returns void as $$
+declare a1 int; a2 int;
+begin
+  select 10,20 into a1;
+end;
+$$ language plpgsql;
+
+-- raise warning
+select plpgsql_check_function('f1()');
+
+create or replace function f1()
+returns void as $$
+declare a1 int; a2 int;
+begin
+  select 10 into a1,a2;
+end;
+$$ language plpgsql;
+
+-- raise warning
+select plpgsql_check_function('f1()');
+
+-- bogus code
+set check_function_bodies to off;
+
+create or replace function f1()
+returns void as $$ 
+adasdfsadf
+$$ language plpgsql;
+
+select plpgsql_check_function('f1()');
+
+drop function f1();
+
#57Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#56)
Re: poll: CHECK TRIGGER?

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

I don't think I'm getting my point across by explaining, so here's a
modified version of the patch that does what I was trying to say.

Minor side point: some of the diff noise in this patch comes from
s/copy_plpgsql_datum/plpgsql_copy_plpgsql_datum/, which seems entirely
useless. The name already contains "plpgsql", and even if it didn't,
there is no particular reason for plpgsql to worry about polluting
global symbol namespace. Nothing else resolves against its symbols
anyway, at least not on any platform we claim to support. I would
therefore also argue against the other renamings like
s/exec_move_row/plpgsql_exec_move_row/.

regards, tom lane

#58Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Tom Lane (#57)
Re: poll: CHECK TRIGGER?

On 04.04.2012 19:32, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:

I don't think I'm getting my point across by explaining, so here's a
modified version of the patch that does what I was trying to say.

Minor side point: some of the diff noise in this patch comes from
s/copy_plpgsql_datum/plpgsql_copy_plpgsql_datum/, which seems entirely
useless. The name already contains "plpgsql", and even if it didn't,
there is no particular reason for plpgsql to worry about polluting
global symbol namespace. Nothing else resolves against its symbols
anyway, at least not on any platform we claim to support. I would
therefore also argue against the other renamings like
s/exec_move_row/plpgsql_exec_move_row/.

Agreed. Looking closer, I'm not sure we even need to expose
exec_move_row() to pl_check.c. It's only used to initialize row-type
function arguments to NULL. But variables that are not explicitly
initialized are NULL anyway, and the checker shouldn't use the values
stored in variables for anything, so I believe that initialization in
function_check() can be replaced with something much simpler or removed
altogether.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#59Pavel Stehule
pavel.stehule@gmail.com
In reply to: Heikki Linnakangas (#58)
Re: poll: CHECK TRIGGER?

2012/4/4 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

On 04.04.2012 19:32, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com>  writes:

I don't think I'm getting my point across by explaining, so here's a
modified version of the patch that does what I was trying to say.

Minor side point: some of the diff noise in this patch comes from
s/copy_plpgsql_datum/plpgsql_copy_plpgsql_datum/, which seems entirely
useless.  The name already contains "plpgsql", and even if it didn't,
there is no particular reason for plpgsql to worry about polluting
global symbol namespace.  Nothing else resolves against its symbols
anyway, at least not on any platform we claim to support.  I would
therefore also argue against the other renamings like
s/exec_move_row/plpgsql_exec_move_row/.

Agreed. Looking closer, I'm not sure we even need to expose exec_move_row()
to pl_check.c. It's only used to initialize row-type function arguments to
NULL. But variables that are not explicitly initialized are NULL anyway, and
the checker shouldn't use the values stored in variables for anything, so I
believe that initialization in function_check() can be replaced with
something much simpler or removed altogether.

+1

Pavel

Show quoted text

--
 Heikki Linnakangas
 EnterpriseDB   http://www.enterprisedb.com

#60Pavel Stehule
pavel.stehule@gmail.com
In reply to: Heikki Linnakangas (#56)
Re: poll: CHECK TRIGGER?

2012/4/4 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

On 30.03.2012 12:36, Pavel Stehule wrote:

2012/3/28 Heikki Linnakangas<heikki.linnakangas@enterprisedb.com>:

In prepare_expr(), you use a subtransaction to catch any ERRORs that
happen

during parsing the expression. That's a good idea, and I think many of
the
check_* functions could be greatly simplified by adopting a similar
approach. Just ereport() any errors you find, and catch them at the
appropriate level, appending the error to the output string. Your current
approach of returning true/false depending on whether there was any
errors
seems tedious.

It cannot be implemented in AST interpret. Without removing some
requested functionality - fatal_errors.

I don't think I'm getting my point across by explaining, so here's a
modified version of the patch that does what I was trying to say. The
general pattern of the checker functions has been changed. Instead of
returning a boolean indicating error or no error, with checks for
fatal_errors scattered around them, the internal checker functions now
return nothing. Any errors are reported with ereport(), and there is a
PG_TRY/CATCH block in a couple of carefully chosen places: in check_stmt(),
so that if you get an error while checking a statement, you continue
checking on the next statement, and in check_assignment() which is now used
by check_expr() and a few other helper functions to basically check all
expressions and SQL statements.

IMHO this makes the code much more readable, now that the control logic of
when to return and when to continue is largely gone. A lot of other cleanup
still needs to be done, I just wanted to demonstrate this ereport+try/catch
idea with this patch.

I checked your idea and it should to work.

What other cleanup (without mentioned in previous mails) do you think?

Regards

Pavel

Show quoted text

--
 Heikki Linnakangas
 EnterpriseDB   http://www.enterprisedb.com

#61Pavel Stehule
pavel.stehule@gmail.com
In reply to: Heikki Linnakangas (#58)
1 attachment(s)
Re: poll: CHECK TRIGGER?

Hello

2012/4/4 Heikki Linnakangas <heikki.linnakangas@enterprisedb.com>:

On 04.04.2012 19:32, Tom Lane wrote:

Heikki Linnakangas<heikki.linnakangas@enterprisedb.com>  writes:

I don't think I'm getting my point across by explaining, so here's a
modified version of the patch that does what I was trying to say.

Minor side point: some of the diff noise in this patch comes from
s/copy_plpgsql_datum/plpgsql_copy_plpgsql_datum/, which seems entirely
useless.  The name already contains "plpgsql", and even if it didn't,
there is no particular reason for plpgsql to worry about polluting
global symbol namespace.  Nothing else resolves against its symbols
anyway, at least not on any platform we claim to support.  I would
therefore also argue against the other renamings like
s/exec_move_row/plpgsql_exec_move_row/.

Agreed. Looking closer, I'm not sure we even need to expose exec_move_row()
to pl_check.c. It's only used to initialize row-type function arguments to
NULL. But variables that are not explicitly initialized are NULL anyway, and
the checker shouldn't use the values stored in variables for anything, so I
believe that initialization in function_check() can be replaced with
something much simpler or removed altogether.

I returned back original names and removed exec_move_row from pl_check.c

This is little bit cleaned Heikki version

Regards

Pavel

Show quoted text

--
 Heikki Linnakangas
 EnterpriseDB   http://www.enterprisedb.com

Attachments:

plpgsql_check_function-2012-04-05-1.patch.gzapplication/x-gzip; name=plpgsql_check_function-2012-04-05-1.patch.gzDownload