schema agnostic functions in language sql

Started by Rob Sargentalmost 6 years ago15 messagesgeneral
Jump to latest
#1Rob Sargent
robjsargent@gmail.com

I'm using postgres 12.2, with multiple identical schema per database
(each with a matching role).  I can write public plpgsql functions
without using a schema identifier and let the runtime search_path find
the correct schema-dependent table.  The same does not appear to be true
for plain sql functions.  The 'parser'? does not recognize the tables
(sans schema qualifier):

ERROR:  relation "<tablename>" does not exist.

I would rather not have to duplicate these across multiple schema - I'll
use plpgsql instead unless I've overlooked some other avenue.

I've found that I can create the function, in public, if I set the
search_path containing one schema (of course) and then successfully
access the function after resetting the search path to use a second
schema.  My "build the world" scripting has so far avoided needing to
know/use any specific role.  Another pipe dream vaporized?

Thanks,
rjs

#2Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Rob Sargent (#1)
Re: schema agnostic functions in language sql

On 5/15/20 3:26 PM, Rob Sargent wrote:

I'm using postgres 12.2, with multiple identical schema per database
(each with a matching role).  I can write public plpgsql functions
without using a schema identifier and let the runtime search_path find
the correct schema-dependent table.  The same does not appear to be true
for plain sql functions.  The 'parser'? does not recognize the tables
(sans schema qualifier):

ERROR:  relation "<tablename>" does not exist.

You probably need to show example code, because I don't see this:

show search_path ;
search_path
------------------------------------------------------------------
public,accounting,history,main,utility,timeclock,table_templates

\d utility.login_info
Table "utility.login_info"
Column | Type | Collation | Nullable |
Default
-------------+--------------------------------+-----------+----------+------------------
user_name | character varying | | not null |
user_pwd | character varying | | not null |
user_role | character varying | | |
ts_insert | timestamp(0) without time zone | | |
now()
ts_update | timestamp(0) without time zone | | |
user_update | character varying(20) | | |
user_insert | character varying(20) | | |
"session_user"()

CREATE FUNCTION getli(varchar) RETURNS login_info AS $$
SELECT * FROM login_info WHERE user_name = $1;
$$ LANGUAGE SQL;

select * from getli('aklaver');
user_name | user_pwd | user_role | ts_insert |
ts_update | user_update | user_insert
-----------+----------+-----------+---------------------+---------------------+-------------+-------------
aklaver | ranger | | 12/29/2012 12:23:17 | 05/15/2020
15:41:14 | | postgres
(1 row)

I would rather not have to duplicate these across multiple schema - I'll
use plpgsql instead unless I've overlooked some other avenue.

I've found that I can create the function, in public, if I set the
search_path containing one schema (of course) and then successfully
access the function after resetting the search path to use a second
schema.  My "build the world" scripting has so far avoided needing to
know/use any specific role.  Another pipe dream vaporized?

Thanks,
rjs

--
Adrian Klaver
adrian.klaver@aklaver.com

#3Rob Sargent
robjsargent@gmail.com
In reply to: Adrian Klaver (#2)
Re: schema agnostic functions in language sql

On 5/15/20 4:43 PM, Adrian Klaver wrote:

On 5/15/20 3:26 PM, Rob Sargent wrote:

I'm using postgres 12.2, with multiple identical schema per database
(each with a matching role).  I can write public plpgsql functions
without using a schema identifier and let the runtime search_path
find the correct schema-dependent table.  The same does not appear to
be true for plain sql functions.  The 'parser'? does not recognize
the tables (sans schema qualifier):

    ERROR:  relation "<tablename>" does not exist.

You probably need to show example code, because I don't see this:

show search_path ;
                           search_path
------------------------------------------------------------------
 public,accounting,history,main,utility,timeclock,table_templates

\d utility.login_info
                               Table "utility.login_info"
   Column    |              Type              | Collation | Nullable |
   Default
-------------+--------------------------------+-----------+----------+------------------

 user_name   | character varying              | | not null |
 user_pwd    | character varying              | | not null |
 user_role   | character varying              | |          |
 ts_insert   | timestamp(0) without time zone | |          | now()
 ts_update   | timestamp(0) without time zone | |          |
 user_update | character varying(20)          | |          |
 user_insert | character varying(20)          | |          |
"session_user"()

CREATE FUNCTION getli(varchar) RETURNS login_info AS $$
    SELECT * FROM login_info WHERE user_name = $1;
$$ LANGUAGE SQL;

select * from getli('aklaver');
 user_name | user_pwd | user_role |      ts_insert      |
ts_update      | user_update | user_insert
-----------+----------+-----------+---------------------+---------------------+-------------+-------------

 aklaver   | ranger   |           | 12/29/2012 12:23:17 | 05/15/2020
15:41:14 |             | postgres
(1 row)

Isn't "utility" in your path above?

Show quoted text

I would rather not have to duplicate these across multiple schema -
I'll use plpgsql instead unless I've overlooked some other avenue.

I've found that I can create the function, in public, if I set the
search_path containing one schema (of course) and then successfully
access the function after resetting the search path to use a second
schema.  My "build the world" scripting has so far avoided needing to
know/use any specific role.  Another pipe dream vaporized?

Thanks,
rjs

#4Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Rob Sargent (#3)
Re: schema agnostic functions in language sql

On 5/15/20 3:53 PM, Rob Sargent wrote:

On 5/15/20 4:43 PM, Adrian Klaver wrote:

On 5/15/20 3:26 PM, Rob Sargent wrote:

I'm using postgres 12.2, with multiple identical schema per database
(each with a matching role).  I can write public plpgsql functions
without using a schema identifier and let the runtime search_path
find the correct schema-dependent table.  The same does not appear to
be true for plain sql functions.  The 'parser'? does not recognize
the tables (sans schema qualifier):

    ERROR:  relation "<tablename>" does not exist.

You probably need to show example code, because I don't see this:

show search_path ;
                           search_path
------------------------------------------------------------------
 public,accounting,history,main,utility,timeclock,table_templates

\d utility.login_info
                               Table "utility.login_info"
   Column    |              Type              | Collation | Nullable |
   Default
-------------+--------------------------------+-----------+----------+------------------

 user_name   | character varying              | | not null |
 user_pwd    | character varying              | | not null |
 user_role   | character varying              | |          |
 ts_insert   | timestamp(0) without time zone | |          | now()
 ts_update   | timestamp(0) without time zone | |          |
 user_update | character varying(20)          | |          |
 user_insert | character varying(20)          | |          |
"session_user"()

CREATE FUNCTION getli(varchar) RETURNS login_info AS $$
    SELECT * FROM login_info WHERE user_name = $1;
$$ LANGUAGE SQL;

select * from getli('aklaver');
 user_name | user_pwd | user_role |      ts_insert      |
ts_update      | user_update | user_insert
-----------+----------+-----------+---------------------+---------------------+-------------+-------------

 aklaver   | ranger   |           | 12/29/2012 12:23:17 | 05/15/2020
15:41:14 |             | postgres
(1 row)

Isn't "utility" in your path above?

Yes. In your OP you had:

"I can write public plpgsql functions without using a schema identifier
and let the runtime search_path find the correct schema-dependent table.
The same does not appear to be true for plain sql functions."

I was showing that search_path works with SQL functions, which you
indicated was not happening for you.

Are you talking about some other case?

I would rather not have to duplicate these across multiple schema -
I'll use plpgsql instead unless I've overlooked some other avenue.

I've found that I can create the function, in public, if I set the
search_path containing one schema (of course) and then successfully
access the function after resetting the search path to use a second
schema.  My "build the world" scripting has so far avoided needing to
know/use any specific role.  Another pipe dream vaporized?

Thanks,
rjs

--
Adrian Klaver
adrian.klaver@aklaver.com

#5Rob Sargent
robjsargent@gmail.com
In reply to: Adrian Klaver (#4)
Re: schema agnostic functions in language sql

On 5/15/20 4:58 PM, Adrian Klaver wrote:

On 5/15/20 3:53 PM, Rob Sargent wrote:

On 5/15/20 4:43 PM, Adrian Klaver wrote:

On 5/15/20 3:26 PM, Rob Sargent wrote:

I'm using postgres 12.2, with multiple identical schema per
database (each with a matching role).  I can write public plpgsql
functions without using a schema identifier and let the runtime
search_path find the correct schema-dependent table.  The same does
not appear to be true for plain sql functions. The 'parser'? does
not recognize the tables (sans schema qualifier):

    ERROR:  relation "<tablename>" does not exist.

You probably need to show example code, because I don't see this:

show search_path ;
                           search_path
------------------------------------------------------------------
 public,accounting,history,main,utility,timeclock,table_templates

\d utility.login_info
                               Table "utility.login_info"
   Column    |              Type              | Collation | Nullable
|    Default
-------------+--------------------------------+-----------+----------+------------------

 user_name   | character varying              | | not null |
 user_pwd    | character varying              | | not null |
 user_role   | character varying              | |          |
 ts_insert   | timestamp(0) without time zone | |          | now()
 ts_update   | timestamp(0) without time zone | |          |
 user_update | character varying(20)          | |          |
 user_insert | character varying(20)          | |          |
"session_user"()

CREATE FUNCTION getli(varchar) RETURNS login_info AS $$
    SELECT * FROM login_info WHERE user_name = $1;
$$ LANGUAGE SQL;

select * from getli('aklaver');
 user_name | user_pwd | user_role | ts_insert      | ts_update     
| user_update | user_insert
-----------+----------+-----------+---------------------+---------------------+-------------+-------------

 aklaver   | ranger   |           | 12/29/2012 12:23:17 | 05/15/2020
15:41:14 |             | postgres
(1 row)

Isn't "utility" in your path above?

Yes. In your OP you had:

"I can write public plpgsql functions without using a schema
identifier and let the runtime search_path find the correct
schema-dependent table.  The same does not appear to be true for plain
sql functions."

I was showing that search_path works with SQL functions, which you
indicated was not happening for you.

Are you talking about some other case?

I'm terribly sorry:  I needed to add that plpgsql works without any
knowledge of the schema, where as defining a plain sql functions does
not work without schema qualification.

sarge=# \dn
  List of schemas
  Name  |  Owner
--------+----------
 base   | postgres
 bulk   | postgres
 gt     | postgres
 public | postgres
 sss    | postgres
(5 rows)

sarge=# show search_path;
   search_path
-----------------
 "$user", public
(1 row)

sarge=#
sarge=# create or replace function public.segment_calls(segid uuid)
returns table (name text, firsti int, lasti int, calls text)
as
$$
select
    p.name,
    s.firstmarker,
    s.lastmarker,
    regexp_replace(substr(g.calls,1+(2*s.firstmarker),
2*(s.lastmarker-s.firstmarker+1))::text, '(..)', ' \1','g') as calls
from
    segment s
    join probandset b on s.probandset_id = b.id
    join people l on b.people_id = l.id
    join people_member m on l.id = m.people_id
    join person p on m.person_id = p.id
    join genotype g on g.markerset_id = s.markerset_id and g.person_id
= p.id
where s.id = segid;
$$
language sql
;

sarge-# ERROR:  relation "segment" does not exist
LINE 11:     segment s
             ^
sarge=# create or replace function public.segment_calls(segid uuid)
returns table (name text, firsti int, lasti int, calls text)
as
$$
begin
select
    p.name,
    s.firstmarker,
    s.lastmarker,
    regexp_replace(substr(g.calls,1+(2*s.firstmarker),
2*(s.lastmarker-s.firstmarker+1))::text, '(..)', ' \1','g') as calls
from
    segment s
    join probandset b on s.probandset_id = b.id
    join people l on b.people_id = l.id
    join people_member m on l.id = m.people_id
    join person p on m.person_id = p.id
    join genotype g on g.markerset_id = s.markerset_id and g.person_id
= p.id
where s.id = segid;
end;
$$
language plpgsql;
sarge-# CREATE FUNCTION

sarge=# Query buffer reset (cleared).
sarge=# \dt gt.*
                  List of relations
 Schema |          Name           | Type  |  Owner
--------+-------------------------+-------+----------
 gt     | chaseable               | table | postgres
 gt     | duo_chaseable           | table | postgres
 gt     | genotype                | table | postgres
 gt     | ld                      | table | postgres
 gt     | probandset              | table | postgres
 gt     | probandset_group        | table | postgres
 gt     | probandset_group_member | table | postgres
 gt     | process                 | table | postgres
 gt     | process_arg             | table | postgres
 gt     | process_input           | table | postgres
 gt     | process_output          | table | postgres
 gt     | projectfile             | table | postgres
 gt     | segment                 | table | postgres
 gt     | segmentset              | table | postgres
 gt     | threshold               | table | postgres
 gt     | threshold_duo_segment   | table | postgres
 gt     | threshold_segment       | table | postgres
(17 rows)

#6David G. Johnston
david.g.johnston@gmail.com
In reply to: Rob Sargent (#5)
Re: schema agnostic functions in language sql

On Fri, May 15, 2020 at 4:07 PM Rob Sargent <robjsargent@gmail.com> wrote:

I'm terribly sorry: I needed to add that plpgsql works without any

knowledge of the schema, where as defining a plain sql functions does
not work without schema qualification.

You need to distinguish between "works" as in "compiles" and "works" as in
"executes".

Yes, SQL and pl/pgsql have very different behaviors when it comes to
compilation and execution. In particular SQL performs parsing earlier
(during creation - just like it does for views) and links the textual query
to its parse result earlier. For pl/pgsql none of that happens until the
function is called. Because of this pl/pgsql allows for ambiguous sql text
to exist and be concretely resolved during execution while SQL does not.

David J.

#7Rob Sargent
robjsargent@gmail.com
In reply to: David G. Johnston (#6)
Re: schema agnostic functions in language sql

On 5/15/20 5:16 PM, David G. Johnston wrote:

On Fri, May 15, 2020 at 4:07 PM Rob Sargent <robjsargent@gmail.com
<mailto:robjsargent@gmail.com>> wrote:

I'm terribly sorry:  I needed to add that plpgsql works without any
knowledge of the schema, where as defining a plain sql functions does
not work without schema qualification.

You need to distinguish between "works" as in "compiles" and "works"
as in "executes".

Yes, SQL and pl/pgsql have very different behaviors when it comes to
compilation and execution.  In particular SQL performs parsing earlier
(during creation - just like it does for views) and links the textual
query to its parse result earlier.  For pl/pgsql none of that happens
until the function is called.  Because of this pl/pgsql allows for
ambiguous sql text to exist and be concretely resolved during
execution while SQL does not.

Thank you for the confirmation.  I'll decide whether I move to plpgsql
or dither with role/search_path in the db creation scripts.
(Both forms, plpgsql and sql, "work" once given the correct context.)

Show quoted text

David J.

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: David G. Johnston (#6)
Re: schema agnostic functions in language sql

"David G. Johnston" <david.g.johnston@gmail.com> writes:

Yes, SQL and pl/pgsql have very different behaviors when it comes to
compilation and execution. In particular SQL performs parsing earlier
(during creation - just like it does for views) and links the textual query
to its parse result earlier. For pl/pgsql none of that happens until the
function is called. Because of this pl/pgsql allows for ambiguous sql text
to exist and be concretely resolved during execution while SQL does not.

I don't think that's accurate. SQL functions are stored as plain text,
just like any other non-C-coded function, and they are not parsed until
execution.

There are big differences from plpgsql of course. For one, it's
possible for a SQL function to be "inlined" into the calling query,
in which case parsing happens during planning of the calling query.
But other than that, I'd expect the execution-time search path
to determine how a SQL function behaves.

Since Rob didn't provide any details, it's far from clear what's
going wrong for him.

regards, tom lane

#9Rob Sargent
robjsargent@gmail.com
In reply to: Tom Lane (#8)
Re: schema agnostic functions in language sql

On 5/15/20 5:41 PM, Tom Lane wrote:

"David G. Johnston" <david.g.johnston@gmail.com> writes:

Yes, SQL and pl/pgsql have very different behaviors when it comes to
compilation and execution. In particular SQL performs parsing earlier
(during creation - just like it does for views) and links the textual query
to its parse result earlier. For pl/pgsql none of that happens until the
function is called. Because of this pl/pgsql allows for ambiguous sql text
to exist and be concretely resolved during execution while SQL does not.

I don't think that's accurate. SQL functions are stored as plain text,
just like any other non-C-coded function, and they are not parsed until
execution.

There are big differences from plpgsql of course. For one, it's
possible for a SQL function to be "inlined" into the calling query,
in which case parsing happens during planning of the calling query.
But other than that, I'd expect the execution-time search path
to determine how a SQL function behaves.

Since Rob didn't provide any details, it's far from clear what's
going wrong for him.

regards, tom lane

Did my message with a sql and plgpsql versions not come through?

I cannot create a plain sql function unless the search_path covers any
table mentioned. Not the case when using plpgsql - no path needed.

I'm ok(ish) with that, unless I've missed some detail.

rjs

#10Adrian Klaver
adrian.klaver@aklaver.com
In reply to: Rob Sargent (#9)
Re: schema agnostic functions in language sql

On 5/15/20 4:58 PM, Rob Sargent wrote:

On 5/15/20 5:41 PM, Tom Lane wrote:

"David G. Johnston" <david.g.johnston@gmail.com> writes:

Yes, SQL and pl/pgsql have very different behaviors when it comes to
compilation and execution.  In particular SQL performs parsing earlier
(during creation - just like it does for views) and links the textual
query
to its parse result earlier.  For pl/pgsql none of that happens until
the
function is called.  Because of this pl/pgsql allows for ambiguous
sql text
to exist and be concretely resolved during execution while SQL does not.

I don't think that's accurate.  SQL functions are stored as plain text,
just like any other non-C-coded function, and they are not parsed until
execution.

There are big differences from plpgsql of course.  For one, it's
possible for a SQL function to be "inlined" into the calling query,
in which case parsing happens during planning of the calling query.
But other than that, I'd expect the execution-time search path
to determine how a SQL function behaves.

Since Rob didn't provide any details, it's far from clear what's
going wrong for him.

            regards, tom lane

Did my message with a sql and plgpsql versions not come through?

I cannot create a plain sql function unless the search_path covers any
table mentioned. Not the case when using plpgsql - no path needed.

But does the plpgsql segment_calls() run?

On other words does:

select * from segment_calls(segid uuid);

work?

I'm ok(ish) with that, unless I've missed some detail.

rjs

--
Adrian Klaver
adrian.klaver@aklaver.com

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Rob Sargent (#9)
Re: schema agnostic functions in language sql

Rob Sargent <robjsargent@gmail.com> writes:

I cannot create a plain sql function unless the search_path covers any
table mentioned. Not the case when using plpgsql - no path needed.

Oh, one of the things that's quite a lot different is the checking
applied at function creation time ;-).

For a SQL function, by default we'll try to parse and analyze the body, so
any unknown tables will draw an error. plpgsql doesn't go further than a
very crude syntax check.

If you don't like that, you can set check_function_bodies = off while
creating your SQL functions. But in any case, it's only related to what
happens at execution if the search path is the same.

regards, tom lane

#12Rob Sargent
robjsargent@gmail.com
In reply to: Adrian Klaver (#10)
Re: schema agnostic functions in language sql

On

regards, tom lane

Did my message with a sql and plgpsql versions not come through?
I cannot create a plain sql function unless the search_path covers any table mentioned. Not the case when using plpgsql - no path needed.

But does the plpgsql segment_calls() run?

On other words does:

select * from segment_calls(segid uuid);

work?

I'm ok(ish) with that, unless I've missed some detail.
rjs

Yes the plpgsql form works but of course requires a sufficient search_path

Show quoted text

--
Adrian Klaver
adrian.klaver@aklaver.com

#13Rob Sargent
robjsargent@gmail.com
In reply to: Tom Lane (#11)
Re: schema agnostic functions in language sql

On May 15, 2020, at 6:03 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Rob Sargent <robjsargent@gmail.com> writes:

I cannot create a plain sql function unless the search_path covers any
table mentioned. Not the case when using plpgsql - no path needed.

Oh, one of the things that's quite a lot different is the checking
applied at function creation time ;-).

For a SQL function, by default we'll try to parse and analyze the body, so
any unknown tables will draw an error. plpgsql doesn't go further than a
very crude syntax check.

If you don't like that, you can set check_function_bodies = off while
creating your SQL functions. But in any case, it's only related to what
happens at execution if the search path is the same.

regards, tom lane

And my fundamental error was thinking the parse of all create function calls was not language specific beyond syntax. Looking back, my use of sql functions has been for inline-able calculations reused in other plpgsql functions.

check_function_body=off may be what I want during the site install as the definitions should be correct in all aspects.

Thank you all
rjs

#14David G. Johnston
david.g.johnston@gmail.com
In reply to: Rob Sargent (#13)
Re: schema agnostic functions in language sql

On Sat, May 16, 2020 at 5:15 AM Rob Sargent <robjsargent@gmail.com> wrote:

check_function_body=off may be what I want during the site install as the
definitions should be correct in all aspects.

You should probably just have one "test" schema and compile your functions
with the non-client test schema in the search_path.

David J.

#15Rob Sargent
robjsargent@gmail.com
In reply to: David G. Johnston (#14)
Re: schema agnostic functions in language sql

On May 16, 2020, at 9:13 AM, David G. Johnston <david.g.johnston@gmail.com> wrote:



On Sat, May 16, 2020 at 5:15 AM Rob Sargent <robjsargent@gmail.com> wrote:
check_function_body=off may be what I want during the site install as the definitions should be correct in all aspects.

You should probably just have one "test" schema and compile your functions with the non-client test schema in the search_path.

David J.

In fact there is just such a thing in the database from which get the DDL to generate new ‘client’ dbs. (This is not commercial in any way). So far I have not needed to bring that template into client space. Do you see a problem with using check_ function_body=off, given that the functions will be developed and tested elsewhere?