Add language validator
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 31 Jan 2013 15:01:32 +0000 (10:01 -0500)
committerPeter Eisentraut <peter@eisentraut.org>
Thu, 31 Jan 2013 19:00:43 +0000 (14:00 -0500)
Refactor some internals to make this possible.  Mainly, FunctionCallInfo
is not available when validating, so avoid accessing that if not
required.  Rename plproxy_compile() to plproxy_compile_and_cache() and
the previously internal fn_compile() to plproxy_compile().  This matches
their purpose better and allows the validator to call plproxy_compile()
without invoking execution-time dependent code.

Many error test cases have changed because the validator catches errors
when the function is created, not when it is called.

Raise the extension version to 2.5.1 to be able to upgrade from
non-validator installations.

14 files changed:
META.json
Makefile
plproxy.control
sql/ext_update_validator.sql [new file with mode: 0644]
sql/plproxy_lang.sql
src/function.c
src/main.c
src/plproxy.h
test/expected/plproxy_dynamic_record.out
test/expected/plproxy_errors.out
test/expected/plproxy_select.out
test/expected/plproxy_split.out
test/expected/plproxy_target.out
test/sql/plproxy_errors.sql

index 671db7d85a8f9e1cb6df2a579ac2dea1552fa34e..6eb08395a41dde357fd2d82558593d89d26abb2a 100644 (file)
--- a/META.json
+++ b/META.json
@@ -14,7 +14,7 @@
          "abstract": "Database partitioning implemented as procedural language",
          "file": "sql/plproxy.sql",
          "docfile": "doc/tutorial.txt",
-         "version": "2.5.0"
+         "version": "2.5.1"
       }
    },
    "prereqs": {
index 1110b49cda4e00a5935d02c3f960a71bba658bb6..fd43e81c4963f1db7a2ddd8418bfc723c04b37de 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,8 @@ EXTENSION  = plproxy
 
 # sync with NEWS, META.json, plproxy.control, debian/changelog
 DISTVERSION = 2.5
-EXTVERSION = 2.5.0
-UPGRADE_VERS = 2.3.0 2.4.0
+EXTVERSION = 2.5.1
+UPGRADE_VERS = 2.3.0 2.4.0 2.5.0
 
 # set to 1 to disallow functions containing SELECT
 NO_SELECT = 0
@@ -112,8 +112,9 @@ sql/$(EXTENSION)--$(EXTVERSION).sql: $(PLPROXY_SQL)
        echo "create extension plproxy;" > sql/plproxy.sql 
        cat $^ > $@
 
-$(foreach v,$(UPGRADE_VERS),sql/plproxy--$(v)--$(EXTVERSION).sql):
-       touch $@
+$(foreach v,$(UPGRADE_VERS),sql/plproxy--$(v)--$(EXTVERSION).sql): sql/ext_update_validator.sql
+       @mkdir -p sql
+       cat $< >$@
 
 sql/plproxy--unpackaged--$(EXTVERSION).sql: sql/ext_unpackaged.sql
        @mkdir -p sql
index 64b12a3a9a2a19352c6175dca66393a05ed89f4a..2da21893743016adda26f3f7d0892be42ccca88e 100644 (file)
@@ -1,6 +1,6 @@
 # plproxy extension
 comment = 'Database partitioning implemented as procedural language'
-default_version = '2.5.0'
+default_version = '2.5.1'
 module_pathname = '$libdir/plproxy'
 relocatable = false
 # schema = pg_catalog
diff --git a/sql/ext_update_validator.sql b/sql/ext_update_validator.sql
new file mode 100644 (file)
index 0000000..6ab7afa
--- /dev/null
@@ -0,0 +1,4 @@
+CREATE FUNCTION plproxy_validator (oid)
+RETURNS void AS 'plproxy' LANGUAGE C;
+
+CREATE OR REPLACE LANGUAGE plproxy HANDLER plproxy_call_handler VALIDATOR plproxy_validator;
index 29d6c9dd2d0ffcc343f9c10049d42c10decb6f95..cdc190609bb12b7a3fc0662fdc7be5051e1321b1 100644 (file)
@@ -3,6 +3,10 @@
 CREATE FUNCTION plproxy_call_handler ()
 RETURNS language_handler AS 'plproxy' LANGUAGE C;
 
+-- validator function
+CREATE FUNCTION plproxy_validator (oid)
+RETURNS void AS 'plproxy' LANGUAGE C;
+
 -- language
-CREATE LANGUAGE plproxy HANDLER plproxy_call_handler;
+CREATE LANGUAGE plproxy HANDLER plproxy_call_handler VALIDATOR plproxy_validator;
 
index f5f1d42c33274d81cf56cf34899a38bae43b2e62..eed5afc784d7dba7d9644f587b8a67011077cacd 100644 (file)
@@ -227,7 +227,7 @@ fn_returns_dynamic_record(HeapTuple proc_tuple)
  * where everything is allocated.
  */
 static ProxyFunction *
-fn_new(FunctionCallInfo fcinfo, HeapTuple proc_tuple)
+fn_new(HeapTuple proc_tuple)
 {
        ProxyFunction *f;
        MemoryContext f_ctx,
@@ -243,7 +243,7 @@ fn_new(FunctionCallInfo fcinfo, HeapTuple proc_tuple)
 
        f = palloc0(sizeof(*f));
        f->ctx = f_ctx;
-       f->oid = fcinfo->flinfo->fn_oid;
+       f->oid = HeapTupleGetOid(proc_tuple);
        plproxy_set_stamp(&f->stamp, proc_tuple);
 
        if (fn_returns_dynamic_record(proc_tuple))
@@ -337,7 +337,6 @@ fn_parse(ProxyFunction *func, HeapTuple proc_tuple)
  */
 static void
 fn_get_arguments(ProxyFunction *func,
-                                FunctionCallInfo fcinfo,
                                 HeapTuple proc_tuple)
 {
        Oid                *types;
@@ -469,28 +468,41 @@ fn_refresh_record(FunctionCallInfo fcinfo,
        func->remote_sql = plproxy_standard_query(func, true);
 }
 
-/* Show part of compilation -- get source and parse */
-static ProxyFunction *
-fn_compile(FunctionCallInfo fcinfo,
+/*
+ * Show part of compilation -- get source and parse
+ *
+ * When called from the validator, validate_only is true, but there is no
+ * fcinfo.
+ */
+ProxyFunction *
+plproxy_compile(FunctionCallInfo fcinfo,
                   HeapTuple proc_tuple,
-                  bool validate)
+                  bool validate_only)
 {
        ProxyFunction *f;
        Form_pg_proc proc_struct;
 
+       Assert(fcinfo || validate_only);
+
        proc_struct = (Form_pg_proc) GETSTRUCT(proc_tuple);
        if (proc_struct->provolatile != PROVOLATILE_VOLATILE)
                elog(ERROR, "PL/Proxy functions must be volatile");
 
-       f = fn_new(fcinfo, proc_tuple);
+       f = fn_new(proc_tuple);
 
        /* keep reference in case of error half-way */
-       partial_func = f;
+       if (!validate_only)
+               partial_func = f;
 
        /* info from system tables */
        fn_set_name(f, proc_tuple);
-       fn_get_return_type(f, fcinfo, proc_tuple);
-       fn_get_arguments(f, fcinfo, proc_tuple);
+       /*
+        * Cannot check return type in validator, because there is no call info to
+        * resolve polymorphic types against.
+        */
+       if (!validate_only)
+               fn_get_return_type(f, fcinfo, proc_tuple);
+       fn_get_arguments(f, proc_tuple);
 
        /* parse body */
        fn_parse(f, proc_tuple);
@@ -498,20 +510,10 @@ fn_compile(FunctionCallInfo fcinfo,
        if (f->dynamic_record && f->remote_sql)
                plproxy_error(f, "SELECT statement not allowed for dynamic RECORD functions");
 
-       /* create SELECT stmt if not specified */
-       if (f->remote_sql == NULL)
-               f->remote_sql = plproxy_standard_query(f, true);
-
-       /* prepare local queries */
-       if (f->cluster_sql)
-               plproxy_query_prepare(f, fcinfo, f->cluster_sql, false);
-       if (f->hash_sql)
-               plproxy_query_prepare(f, fcinfo, f->hash_sql, true);
-       if (f->connect_sql)
-               plproxy_query_prepare(f, fcinfo, f->connect_sql, false);
-
        /* sanity check */
-       if (f->run_type == R_ALL && !fcinfo->flinfo->fn_retset)
+       if (f->run_type == R_ALL && (fcinfo
+                                                                ? !fcinfo->flinfo->fn_retset
+                                                                : !get_func_retset(HeapTupleGetOid(proc_tuple))))
                plproxy_error(f, "RUN ON ALL requires set-returning function");
 
        return f;
@@ -521,7 +523,7 @@ fn_compile(FunctionCallInfo fcinfo,
  * Compile and cache PL/Proxy function.
  */
 ProxyFunction *
-plproxy_compile(FunctionCallInfo fcinfo, bool validate)
+plproxy_compile_and_cache(FunctionCallInfo fcinfo)
 {
        ProxyFunction *f;
        HeapTuple       proc_tuple;
@@ -554,7 +556,19 @@ plproxy_compile(FunctionCallInfo fcinfo, bool validate)
 
        if (!f)
        {
-               f = fn_compile(fcinfo, proc_tuple, validate);
+               f = plproxy_compile(fcinfo, proc_tuple, false);
+
+               /* create SELECT stmt if not specified */
+               if (f->remote_sql == NULL)
+                       f->remote_sql = plproxy_standard_query(f, true);
+
+               /* prepare local queries */
+               if (f->cluster_sql)
+                       plproxy_query_prepare(f, fcinfo, f->cluster_sql, false);
+               if (f->hash_sql)
+                       plproxy_query_prepare(f, fcinfo, f->hash_sql, true);
+               if (f->connect_sql)
+                       plproxy_query_prepare(f, fcinfo, f->connect_sql, false);
 
                fn_cache_insert(f);
 
index 2e0f736034c0c75c6bc85d810c8683af0f370a10..c459aaefd5aa1a8fd9908045818cd6905b79a612 100644 (file)
@@ -57,6 +57,7 @@ PG_MODULE_MAGIC;
 #endif
 
 PG_FUNCTION_INFO_V1(plproxy_call_handler);
+PG_FUNCTION_INFO_V1(plproxy_validator);
 
 /*
  * Centralised error reporting.
@@ -178,7 +179,7 @@ compile_and_execute(FunctionCallInfo fcinfo)
        plproxy_startup_init();
 
        /* compile code */
-       func = plproxy_compile(fcinfo, false);
+       func = plproxy_compile_and_cache(fcinfo);
 
        /* get actual cluster to run on */
        cluster = plproxy_find_cluster(func, fcinfo);
@@ -266,3 +267,24 @@ plproxy_call_handler(PG_FUNCTION_ARGS)
        }
        return ret;
 }
+
+/*
+ * This function is called when a PL/Proxy function is created to
+ * check the syntax.
+ */
+Datum
+plproxy_validator(PG_FUNCTION_ARGS)
+{
+       Oid oid = PG_GETARG_OID(0);
+       HeapTuple       proc_tuple;
+
+       proc_tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(oid), 0, 0, 0);
+       if (!HeapTupleIsValid(proc_tuple))
+               elog(ERROR, "cache lookup failed for function %u", oid);
+
+       plproxy_compile(NULL, proc_tuple, true);
+
+       ReleaseSysCache(proc_tuple);
+
+       PG_RETURN_VOID();
+}
index d478e1c2f0639a62fb600af4ea4186c3fd82f171..5c5c1af50c09af4ae7d76fb317fb32cc50140c69 100644 (file)
@@ -434,6 +434,7 @@ typedef struct ProxyFunction
 
 /* main.c */
 Datum          plproxy_call_handler(PG_FUNCTION_ARGS);
+Datum          plproxy_validator(PG_FUNCTION_ARGS);
 void           plproxy_error(ProxyFunction *func, const char *fmt, ...)
        __attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
 void           plproxy_remote_error(ProxyFunction *func, ProxyConnection *conn, const PGresult *res, bool iserr);
@@ -445,7 +446,8 @@ char           *plproxy_func_strdup(ProxyFunction *func, const char *s);
 int                    plproxy_get_parameter_index(ProxyFunction *func, const char *ident);
 bool           plproxy_split_add_ident(ProxyFunction *func, const char *ident);
 void           plproxy_split_all_arrays(ProxyFunction *func);
-ProxyFunction *plproxy_compile(FunctionCallInfo fcinfo, bool validate);
+ProxyFunction *plproxy_compile_and_cache(FunctionCallInfo fcinfo);
+ProxyFunction *plproxy_compile(FunctionCallInfo fcinfo, HeapTuple proc_tuple, bool validate_only);
 
 /* execute.c */
 void           plproxy_exec(ProxyFunction *func, FunctionCallInfo fcinfo);
index fc730dc63aa32045dfc3003501ea2afbe80aa5b8..39ce442691f0f1bacdf2831acdf6064e4145052d 100644 (file)
@@ -45,5 +45,9 @@ returns setof record as $x$
     run on all;
     select id, username from dynamic_query_test;
 $x$ language plproxy;
-select * from dynamic_query_select() as (id integer, username text);
 ERROR:  PL/Proxy function public.dynamic_query_select(0): SELECT statement not allowed for dynamic RECORD functions
+select * from dynamic_query_select() as (id integer, username text);
+ERROR:  function dynamic_query_select() does not exist
+LINE 1: select * from dynamic_query_select() as (id integer, usernam...
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
index 49acc3ac2a217c4144087b4623eef8d0f49b722b..ec6a4fc823a497d318fd0bef468c8248878e9dd7 100644 (file)
@@ -14,8 +14,12 @@ returns text as $$
     cluster 'testcluster';
     run on hashtext($2);
 $$ language plproxy;
-select * from test_err2('dat');
 ERROR:  PL/Proxy function public.test_err2(1): Compile error at line 3: invalid argument reference: $2
+select * from test_err2('dat');
+ERROR:  function test_err2(unknown) does not exist
+LINE 1: select * from test_err2('dat');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function test_err3(dat text)
 returns text as $$
     cluster 'nonexists';
@@ -62,34 +66,51 @@ returns record as $$
     run on hashtext(dat);
     select dat as res2, 'foo' as res1;
 $$ language plproxy;
-select * from test_map_err4('dat');
 ERROR:  PL/Proxy function public.test_map_err4(1): Compile error at line 5: CLUSTER statement missing
+select * from test_map_err4('dat');
+ERROR:  function test_map_err4(unknown) does not exist
+LINE 1: select * from test_map_err4('dat');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function test_variadic_err(first text, rest variadic text[])
 returns text as $$
     cluster 'testcluster';
 $$ language plproxy;
-select * from test_variadic_err('dat', 'dat', 'dat');
 ERROR:  PL/Proxy does not support variadic args
+select * from test_variadic_err('dat', 'dat', 'dat');
+ERROR:  function test_variadic_err(unknown, unknown, unknown) does not exist
+LINE 1: select * from test_variadic_err('dat', 'dat', 'dat');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function test_volatile_err(dat text)
 returns text
 stable
 as $$
     cluster 'testcluster';
 $$ language plproxy;
-select * from test_volatile_err('dat');
 ERROR:  PL/Proxy functions must be volatile
+select * from test_volatile_err('dat');
+ERROR:  function test_volatile_err(unknown) does not exist
+LINE 1: select * from test_volatile_err('dat');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function test_pseudo_arg_err(dat cstring)
 returns text
 as $$
     cluster 'testcluster';
 $$ language plproxy;
-select * from test_pseudo_arg_err(textout('dat'));
 ERROR:  PL/Proxy function public.test_pseudo_arg_err(0): unsupported pseudo type: cstring (2275)
+select * from test_pseudo_arg_err(textout('dat'));
+ERROR:  function test_pseudo_arg_err(cstring) does not exist
+LINE 1: select * from test_pseudo_arg_err(textout('dat'));
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function test_pseudo_ret_err(dat text)
 returns cstring
 as $$
     cluster 'testcluster';
 $$ language plproxy;
+-- not detected in validator
 select * from test_pseudo_ret_err('dat');
 ERROR:  PL/Proxy function public.test_pseudo_ret_err(0): unsupported pseudo type: cstring (2275)
 create function test_runonall_err(dat text)
@@ -98,5 +119,9 @@ as $$
     cluster 'testcluster';
     run on all;
 $$ language plproxy;
-select * from test_runonall_err('dat');
 ERROR:  PL/Proxy function public.test_runonall_err(1): RUN ON ALL requires set-returning function
+select * from test_runonall_err('dat');
+ERROR:  function test_runonall_err(unknown) does not exist
+LINE 1: select * from test_runonall_err('dat');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
index 55f0e1b8eaea12914c7e07db5b0744ddc9f4c864..4b67e5362f75e962b4288823888dce77df2b59c5 100644 (file)
@@ -33,8 +33,12 @@ returns integer as $$
     select id from sel_test where username = xuser;
     select id from sel_test where username = xuser;
 $$ language plproxy;
-select * from test_select_err('user', true);
 ERROR:  PL/Proxy function public.test_select_err(2): Compile error at line 5: Only one SELECT statement allowed
+select * from test_select_err('user', true);
+ERROR:  function test_select_err(unknown, boolean) does not exist
+LINE 1: select * from test_select_err('user', true);
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function get_zero()
 returns setof integer as $x$
     cluster 'testcluster';
index 6f5e017f6b02aac93dd443da740c3948e0ec09a3..7794fd4f3bda2a255f4ea0ec3555ba1f0e8e5318 100644 (file)
@@ -31,23 +31,39 @@ $$ language sql;
 -- invalid arg reference
 create or replace function test_array(a text[], b text[], c text) returns setof text as
 $$ split $4; cluster 'testcluster'; run on 0;$$ language plproxy;
-select * from test_array(array['a'], array['g'], 'foo');
 ERROR:  PL/Proxy function public.test_array(3): Compile error at line 1: invalid argument reference: $4
+select * from test_array(array['a'], array['g'], 'foo');
+ERROR:  function test_array(text[], text[], unknown) does not exist
+LINE 1: select * from test_array(array['a'], array['g'], 'foo');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 -- invalid arg name
 create or replace function test_array(a text[], b text[], c text) returns setof text as
 $$ split x; cluster 'testcluster'; run on 0; $$ language plproxy;
-select * from test_array(array['a'], array['b', 'c'], 'foo');
 ERROR:  PL/Proxy function public.test_array(3): Compile error at line 1: invalid argument reference: x
+select * from test_array(array['a'], array['b', 'c'], 'foo');
+ERROR:  function test_array(text[], text[], unknown) does not exist
+LINE 1: select * from test_array(array['a'], array['b', 'c'], 'foo')...
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 -- cannot split more than once
 create or replace function test_array(a text[], b text[], c text) returns setof text as
 $$ split a, b, b; cluster 'testcluster'; run on 0; $$ language plproxy;
-select * from test_array(array['a'], array['b', 'c'], 'foo');
 ERROR:  PL/Proxy function public.test_array(3): SPLIT parameter specified more than once: b
+select * from test_array(array['a'], array['b', 'c'], 'foo');
+ERROR:  function test_array(text[], text[], unknown) does not exist
+LINE 1: select * from test_array(array['a'], array['b', 'c'], 'foo')...
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 -- attempt to split non-array
 create or replace function test_array(a text[], b text[], c text) returns setof text as
 $$ split $3; cluster 'testcluster'; run on 0;$$ language plproxy;
-select * from test_array(array['a'], array['g'], 'foo');
 ERROR:  PL/Proxy function public.test_array(3): SPLIT parameter is not an array: $3
+select * from test_array(array['a'], array['g'], 'foo');
+ERROR:  function test_array(text[], text[], unknown) does not exist
+LINE 1: select * from test_array(array['a'], array['g'], 'foo');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 -- array size/dimensions mismatch
 create or replace function test_array(a text[], b text[], c text) returns setof text as
 $$ split a, b; cluster 'testcluster'; run on 0; $$ language plproxy;
index 2c5c7c8beb0bf4aa67a8c0aa9338ca46b7a10f82..3b442cc9b15f838de55397cef3f953847ca911f7 100644 (file)
@@ -27,8 +27,12 @@ returns text as $$
     target test_target_dst;
     target test_target_dst;
 $$ language plproxy;
-select * from test_target_err1('asd');
 ERROR:  PL/Proxy function public.test_target_err1(1): Compile error at line 5: Only one TARGET statement allowed
+select * from test_target_err1('asd');
+ERROR:  function test_target_err1(unknown) does not exist
+LINE 1: select * from test_target_err1('asd');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 create function test_target_err2(xuser text)
 returns text as $$
     cluster 'testcluster';
@@ -36,5 +40,9 @@ returns text as $$
     target test_target_dst;
     select 1;
 $$ language plproxy;
-select * from test_target_err2('asd');
 ERROR:  PL/Proxy function public.test_target_err2(1): Compile error at line 6: TARGET cannot be used with SELECT
+select * from test_target_err2('asd');
+ERROR:  function test_target_err2(unknown) does not exist
+LINE 1: select * from test_target_err2('asd');
+                      ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
index 09f85a22e7a247959b9600d25f97273a24e83362..724ccb63499c5fdaacb453737502ab401ada44af 100644 (file)
@@ -83,6 +83,7 @@ returns cstring
 as $$
     cluster 'testcluster';
 $$ language plproxy;
+-- not detected in validator
 select * from test_pseudo_ret_err('dat');
 
 create function test_runonall_err(dat text)