PL/PgSQL "bare" function calls
I'd like to make it possible to perform function calls in PL/PgSQL
without needing to use PERFORM. I think this would be useful because (a)
it is closer to how PL/SQL works (b) PERFORM is (IMHO) a kludge, and
making it unnecessary would make programming in PL/PgSQL more natural.
Attached is a proof of concept patch that implements this. With this
patch, you can now write functions like so:
CREATE OR REPLACE FUNCTION some_func() RETURNS INT AS
'BEGIN
call_some_function();
RETURN 5;
END;
' LANGUAGE 'plpgsql';
Known issues with the patch:
(1) It seems to induce an intermittent segfault in the regression tests
on one of my machines (but not any others) which I need to fix.
(2) We should probably allow the name of a function to be double-quoted
so that it is possible to call functions with unusual names (mixed-case
and so on). I believe this should be possible, I just haven't
implemented it yet.
(3) The parser must distinguish between two cases when it sees an
unknown word (T_WORD) beginning a statement. The word could be the
beginning of a SQL statement (stmt_execsql in the grammar), such as:
UPDATE ...;
or the name of a function in a function call:
invoke_func(...);
The patch currently distinguishes between these cases by looking at the
next token -- if it is a left parenthesis, the patch assumes it is a
function call, otherwise it assumes it is a SQL statement. Is this the
best approach?
(Another method would be to teach the PL/PgSQL lexer about the basic SQL
keywords like UPDATE, INSERT, and so on, and then distinguish the two
cases that way. This would impose a maintenance burden when new SQL
commands are added, so I didn't adopt this approach.)
(4) This is proof-of-concept, so there's some mopup I still need to do
(for example, more extensive regression tests, and consider whether it
is better to reuse PLpgSQL_stmt_perform or to invent a new statement
type for this feature, update the docs, etc.)
Any comments?
-Neil
On Thu, 2004-09-16 at 00:06, Neil Conway wrote:
Attached is a proof of concept patch that implements this.
Woops, the patch is really attached this time.
-Neil
Attachments:
plpgsql-bare-function-call-11.patchtext/x-patch; charset=iso-8859-1; name=plpgsql-bare-function-call-11.patchDownload
Index: src/pl/plpgsql/src/gram.y
===================================================================
RCS file: /home/neilc/private-cvsroot/pgsql-server/src/pl/plpgsql/src/gram.y,v
retrieving revision 1.62
diff -c -r1.62 gram.y
*** src/pl/plpgsql/src/gram.y 14 Sep 2004 23:46:46 -0000 1.62
--- src/pl/plpgsql/src/gram.y 15 Sep 2004 01:13:05 -0000
***************
*** 526,532 ****
plpgsql_convert_ident(yytext, &name, 1);
/* name should be malloc'd for use as varname */
$$.name = strdup(name);
! $$.lineno = plpgsql_scanner_lineno();
pfree(name);
}
;
--- 526,532 ----
plpgsql_convert_ident(yytext, &name, 1);
/* name should be malloc'd for use as varname */
$$.name = strdup(name);
! $$.lineno = plpgsql_scanner_lineno();
pfree(name);
}
;
***************
*** 1315,1328 ****
stmt_execsql : execsql_start lno
{
! PLpgSQL_stmt_execsql *new;
! new = malloc(sizeof(PLpgSQL_stmt_execsql));
! new->cmd_type = PLPGSQL_STMT_EXECSQL;
! new->lineno = $2;
! new->sqlstmt = read_sql_stmt($1);
! $$ = (PLpgSQL_stmt *)new;
}
;
--- 1315,1349 ----
stmt_execsql : execsql_start lno
{
! int tok = yylex();
! plpgsql_push_back_token(tok);
! if (tok == '(')
! {
! PLpgSQL_stmt_perform *new;
! char *prefix;
! prefix = malloc(strlen($1) + 7 + 1);
! sprintf(prefix, "SELECT %s", $1);
!
! new = malloc(sizeof(PLpgSQL_stmt_perform));
! new->cmd_type = PLPGSQL_STMT_PERFORM;
! new->lineno = $2;
! new->expr = read_sql_stmt(prefix);
!
! $$ = (PLpgSQL_stmt *)new;
! free(prefix);
! }
! else
! {
! PLpgSQL_stmt_execsql *new;
! new = malloc(sizeof(PLpgSQL_stmt_execsql));
! new->cmd_type = PLPGSQL_STMT_EXECSQL;
! new->lineno = $2;
! new->sqlstmt = read_sql_stmt($1);
!
! $$ = (PLpgSQL_stmt *)new;
! }
}
;
***************
*** 1540,1545 ****
--- 1561,1567 ----
execsql_start : T_WORD
{ $$ = strdup(yytext); }
+ /* XXX: why do we need this case? */
| T_ERROR
{ $$ = strdup(yytext); }
;
Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /home/neilc/private-cvsroot/pgsql-server/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.119
diff -c -r1.119 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c 13 Sep 2004 20:09:20 -0000 1.119
--- src/pl/plpgsql/src/pl_exec.c 15 Sep 2004 01:13:06 -0000
***************
*** 1166,1177 ****
PLpgSQL_expr *expr = stmt->expr;
int rc;
- /*
- * If not already done create a plan for this expression
- */
- if (expr->plan == NULL)
- exec_prepare_plan(estate, expr);
-
rc = exec_run_select(estate, expr, 0, NULL);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
--- 1166,1171 ----
***************
*** 4152,4158 ****
expr->expr_simple_expr = NULL;
/*
! * 1. We can only evaluate queries that resulted in one single
* execution plan
*/
if (list_length(spi_plan->ptlist) != 1)
--- 4146,4152 ----
expr->expr_simple_expr = NULL;
/*
! * 1. We can only evaluate queries that resulted in a single
* execution plan
*/
if (list_length(spi_plan->ptlist) != 1)
Index: src/test/regress/sql/plpgsql.sql
===================================================================
RCS file: /home/neilc/private-cvsroot/pgsql-server/src/test/regress/sql/plpgsql.sql,v
retrieving revision 1.15
diff -c -r1.15 plpgsql.sql
*** src/test/regress/sql/plpgsql.sql 10 Sep 2004 18:40:09 -0000 1.15
--- src/test/regress/sql/plpgsql.sql 15 Sep 2004 01:13:06 -0000
***************
*** 1750,1752 ****
--- 1750,1778 ----
drop function trap_foreign_key(int);
drop function trap_foreign_key_2();
+
+ --
+ -- "bare" function calls
+ --
+
+ create function called_func() returns int as '
+ BEGIN
+ RAISE NOTICE ''called_func() invoked'';
+ RETURN 5;
+ END;
+ ' language 'plpgsql';
+
+ create function calling_func(param int) returns int as '
+ BEGIN
+ called_func();
+ CALLED_FUNC();
+ if param = 100 then
+ called_func();
+ else
+ called_func();
+ end if;
+ RETURN 10;
+ END;
+ ' language 'plpgsql';
+
+ select calling_func(15);
Neil Conway <neilc@samurai.com> writes:
(3) The parser must distinguish between two cases when it sees an
unknown word (T_WORD) beginning a statement. The word could be the
beginning of a SQL statement (stmt_execsql in the grammar), such as:
UPDATE ...;
or the name of a function in a function call:
invoke_func(...);
The patch currently distinguishes between these cases by looking at the
next token -- if it is a left parenthesis, the patch assumes it is a
function call, otherwise it assumes it is a SQL statement. Is this the
best approach?
That seems fairly unworkable. For example
SELECT (2,3,4);
is valid SQL. Also I'm not sure if you can extend this to cope with
schema-qualified function names.
regards, tom lane
Tom Lane wrote:
Neil Conway <neilc@samurai.com> writes:
(3) The parser must distinguish between two cases when it sees an
unknown word (T_WORD) beginning a statement. The word could be the
beginning of a SQL statement (stmt_execsql in the grammar), such as:UPDATE ...;
or the name of a function in a function call:
invoke_func(...);
The patch currently distinguishes between these cases by looking at the
next token -- if it is a left parenthesis, the patch assumes it is a
function call, otherwise it assumes it is a SQL statement. Is this the
best approach?That seems fairly unworkable. For example
SELECT (2,3,4);
is valid SQL. Also I'm not sure if you can extend this to cope with
schema-qualified function names.
ISTM that this is being done at the wrong level anyway. I'd like to see
a facility available in our SQL, e.g.
CALL foo();
with the restriction that foo() should be declared to return void. Of
course, that doesn't remove the keyword requirement as Neil wanted, but
doing that would probably require a lot more work - we'd have to make
procedures a whole lot closer to first-class objects.
cheers
andrew
Andrew Dunstan wrote:
ISTM that this is being done at the wrong level anyway. I'd like to see
a facility available in our SQL, e.g.CALL foo();
with the restriction that foo() should be declared to return void. Of
course, that doesn't remove the keyword requirement as Neil wanted, but
doing that would probably require a lot more work - we'd have to make
procedures a whole lot closer to first-class objects.
I agree with this, except that foo() should be a PROCEDURE, not a FUNCTION.
Joe
On Thu, 2004-09-16 at 01:05, Tom Lane wrote:
That seems fairly unworkable. For example
SELECT (2,3,4);
is valid SQL.
Good point. The disambiguation algorithm I suggested isn't sufficient,
but I think there ought to be _some_ reasonable algorithm.
From glancing over the SQL commands, I believe SELECT is the only case
where a SQL statement starts with a T_WORD token followed by a left
parenthesis (correct me if I'm mistaken). If that's the case, one
solution would be to just special-case SELECT: if the name of the
"function" is 'select', we treat it as a SQL statement and not a
function call. Of course, this wouldn't apply if the function name is
double-quoted or schema-qualified.
Another technique would be to delay distinguishing between these two
cases until the function is first invoked; then lookup the function name
in pg_proc, and if a candidate function with that name is found, assume
it's a function call. I don't really like this technique, though.
Also I'm not sure if you can extend this to cope with
schema-qualified function names.
Sorry, I forgot to mention that -- yes, that is intended.
-Neil
On Thu, 2004-09-16 at 01:19, Andrew Dunstan wrote:
ISTM that this is being done at the wrong level anyway. I'd like to see
a facility available in our SQL, e.g.CALL foo();
with the restriction that foo() should be declared to return void.
I think these are two distinct issues. The patch I sent along is
intended to make it more natural to invoke functions (and eventually
procedures) from PL/PgSQL, whereas adding support for CALL to SQL is
part of proper support for stored procedures. Gavin and I are hoping to
send a proposal for the latter to -hackers in a few days.
-Neil
Neil Conway <neilc@samurai.com> writes:
whereas adding support for CALL to SQL is part of proper support for stored
procedures. Gavin and I are hoping to send a proposal for the latter to
-hackers in a few days.
What is the point of stored procedures being distinct from functions anyways?
Is there any real difference other than the irregular calling syntax? Is there
anything you can't do with functions that you can do with procedures? Or is it
purely a question of satisfying a spec or providing a more Oracle compatible
syntax?
--
greg
On Thu, 16 Sep 2004, Greg Stark wrote:
Neil Conway <neilc@samurai.com> writes:
whereas adding support for CALL to SQL is part of proper support for stored
procedures. Gavin and I are hoping to send a proposal for the latter to
-hackers in a few days.What is the point of stored procedures being distinct from functions anyways?
Is there any real difference other than the irregular calling syntax? Is there
anything you can't do with functions that you can do with procedures? Or is it
purely a question of satisfying a spec or providing a more Oracle compatible
syntax?
SQL-invoked procedures (ie, stored procedures) differ in two ways from
functions. These are:
1) Procedures do not return a value.
2) Arguments have 'parameter modes'. These modes are: IN - an input
parameter, which has been initialised to some value and is read-only; OUT
- an uninitialised parameter which can be written to; IN OUT - which has
the properties of each of the above.
What this actually means is that you can declare a procedure as follows:
CREATE PROCEDURE foo(IN bar INT, OUT baz INT, OUT bat INT, ...)
That is, a procedure can actually 'return' many values from a call. We can
do this with composite types but, speaking from experience, this can make
migration from PL/SQL just that much harder.
The other thing which SQL-invoked procedures necessitate is support for
the concept of a 'variable'. The reason being that if you use CALL in top
level SQL, you cannot make reference to a field of a relation in any
meaningful way and passing a column reference, for example, as an OUT
parameter does make any sense.
So, SQL2003 defines a few types of variables but the one people may be
most familiar with is the host parameter. This is a named variable which
is referenced as :foo.
I'm putting together a much more detailed email on all this which I hope
to send out in the next few days.
Thanks,
Gavin
Neil Conway <neilc@samurai.com> writes:
On Thu, 2004-09-16 at 01:19, Andrew Dunstan wrote:
ISTM that this is being done at the wrong level anyway.
I think these are two distinct issues.
I think Andrew has a point: why aren't they the same issue? It would
certainly be no harder to support
func( ... );
as a SQL statement than as something allowed only in plpgsql. I think
it'd be easier to make it work in the full bison grammar than with some
lookahead hack in plpgsql.
regards, tom lane
On Fri, 2004-09-17 at 00:34, Tom Lane wrote:
I think Andrew has a point: why aren't they the same issue? It would
certainly be no harder to support
func( ... );
as a SQL statement than as something allowed only in plpgsql.
If there's a consensus that it is better to modify the main grammar so
that unadorned function calls are legal anywhere, that's fine with me.
If anyone doesn't want this, speak up now.
(Note that we need to support CALL proc(...); in SQL for standards
compliance in any event.)
I think it'd be easier to make it work in the full bison grammar
than with some lookahead hack in plpgsql.
Well, as it turns out, it's easy to do in PL/PgSQL as well. The SELECT
issue you mentioned doesn't actually pose a problem, because
SELECT (2, 3, 4);
is _not_ legal SQL in PL/PgSQL (PL/PgSQL requires SELECT INTO). Also, we
get support for double-quotes and schema-qualified function names for
free, because of how the PL/PgSQL scanner works.
-Neil
Neil Conway <neilc@samurai.com> writes:
On Fri, 2004-09-17 at 00:34, Tom Lane wrote:
I think Andrew has a point: why aren't they the same issue?
(Note that we need to support CALL proc(...); in SQL for standards
compliance in any event.)
Right. I'm thinking we could effectively make the CALL keyword optional
(though of course this is just speculation that it can be done without
any parsing conflicts).
Well, as it turns out, it's easy to do in PL/PgSQL as well. The SELECT
issue you mentioned doesn't actually pose a problem, because
SELECT (2, 3, 4);
is _not_ legal SQL in PL/PgSQL (PL/PgSQL requires SELECT INTO).
So? Lookahead won't help you if the INTO is at the end.
regards, tom lane