From: Pavan Deolasee Date: Thu, 11 Aug 2016 05:07:13 +0000 (+0530) Subject: Merge remote-tracking branch 'remotes/PGSQL/REL9_5_STABLE' into XL9_5_STABLE X-Git-Tag: XL9_5_R1_3~8 X-Git-Url: http://git.postgresql.org/gitweb/static/gitweb.js?a=commitdiff_plain;h=78c0f4b38bee5b360b98f177c7ba8febde99f183;p=postgres-xl.git Merge remote-tracking branch 'remotes/PGSQL/REL9_5_STABLE' into XL9_5_STABLE --- 78c0f4b38bee5b360b98f177c7ba8febde99f183 diff --cc configure index d5b5fd4b7c,94c088c62c..3f8dbb37ca --- a/configure +++ b/configure @@@ -1,8 -1,8 +1,8 @@@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. - # Generated by GNU Autoconf 2.69 for PostgreSQL 9.5.3 (Postgres-XL 9.5r1.2). -# Generated by GNU Autoconf 2.69 for PostgreSQL 9.5.4. ++# Generated by GNU Autoconf 2.69 for PostgreSQL 9.5.4 (Postgres-XL 9.5r1.2). # -# Report bugs to . +# Report bugs to . # # # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. @@@ -582,10 -582,9 +582,10 @@@ MAKEFLAGS # Identity of this package. PACKAGE_NAME='PostgreSQL' PACKAGE_TARNAME='postgresql' - PACKAGE_VERSION='9.5.3 (Postgres-XL 9.5r1.2)' -PACKAGE_VERSION='9.5.4' -PACKAGE_STRING='PostgreSQL 9.5.4' -PACKAGE_BUGREPORT='pgsql-bugs@postgresql.org' ++PACKAGE_VERSION='9.5.4 (Postgres-XL 9.5r1.2)' +PACKAGE_XC_VERSION='9.5r1.2' - PACKAGE_STRING='PostgreSQL 9.5.3 (Postgres-XL 9.5r1.2)' ++PACKAGE_STRING='PostgreSQL 9.5.4 (Postgres-XL 9.5r1.2)' +PACKAGE_BUGREPORT='bugs@postgres-xl.org' PACKAGE_URL='' ac_unique_file="src/backend/access/common/heaptuple.c" @@@ -1399,7 -1395,7 +1399,7 @@@ if test "$ac_init_help" = "long"; the # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF - \`configure' configures PostgreSQL 9.5.3 (Postgres-XL 9.5r1.2) to adapt to many kinds of systems. -\`configure' configures PostgreSQL 9.5.4 to adapt to many kinds of systems. ++\`configure' configures PostgreSQL 9.5.4 (Postgres-XL 9.5r1.2) to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@@ -1464,7 -1460,7 +1464,7 @@@ f if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of PostgreSQL 9.5.3 (Postgres-XL 9.5r1.2):";; - short | recursive ) echo "Configuration of PostgreSQL 9.5.4:";; ++ short | recursive ) echo "Configuration of PostgreSQL 9.5.4 (Postgres-XL 9.5r1.2):";; esac cat <<\_ACEOF @@@ -1614,7 -1610,7 +1614,7 @@@ f test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF - PostgreSQL configure 9.5.3 (Postgres-XL 9.5r1.2) -PostgreSQL configure 9.5.4 ++PostgreSQL configure 9.5.4 (Postgres-XL 9.5r1.2) generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@@ -2325,7 -2321,7 +2325,7 @@@ cat >config.log <<_ACEO This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. - It was created by PostgreSQL $as_me 9.5.3 (Postgres-XL 9.5r1.2), which was -It was created by PostgreSQL $as_me 9.5.4, which was ++It was created by PostgreSQL $as_me 9.5.4 (Postgres-XL 9.5r1.2), which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@@ -16245,7 -16195,7 +16245,7 @@@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_wr # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" - This file was extended by PostgreSQL $as_me 9.5.3 (Postgres-XL 9.5r1.2), which was -This file was extended by PostgreSQL $as_me 9.5.4, which was ++This file was extended by PostgreSQL $as_me 9.5.4 (Postgres-XL 9.5r1.2), which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@@ -16315,7 -16265,7 +16315,7 @@@ _ACEO cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ - PostgreSQL config.status 9.5.3 (Postgres-XL 9.5r1.2) -PostgreSQL config.status 9.5.4 ++PostgreSQL config.status 9.5.4 (Postgres-XL 9.5r1.2) configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --cc configure.in index 414a5312a8,757aa7f343..569cc3f3b8 --- a/configure.in +++ b/configure.in @@@ -17,7 -17,7 +17,7 @@@ dnl Read the Autoconf manual for detail dnl m4_pattern_forbid(^PGAC_)dnl to catch undefined macros - AC_INIT([PostgreSQL], [9.5.3 (Postgres-XL 9.5r1.2)], [bugs@postgres-xl.org]) -AC_INIT([PostgreSQL], [9.5.4], [pgsql-bugs@postgresql.org]) ++AC_INIT([PostgreSQL], [9.5.4 (Postgres-XL 9.5r1.2)], [bugs@postgres-xl.org]) m4_if(m4_defn([m4_PACKAGE_VERSION]), [2.69], [], [m4_fatal([Autoconf version 2.69 is required. Untested combinations of 'autoconf' and PostgreSQL versions are not diff --cc contrib/citext/expected/citext_1.out index d2791a5eb4,462d42a3bd..6515fbd571 --- a/contrib/citext/expected/citext_1.out +++ b/contrib/citext/expected/citext_1.out @@@ -519,19 -502,17 +502,17 @@@ SELECT name FROM srt WHERE name !~ 'A$ (3 rows) -- SIMILAR TO should be case-insensitive. -SELECT name FROM srt WHERE name SIMILAR TO '%a.*'; +SELECT name FROM srt WHERE name SIMILAR TO '%a.*' order by name; name ------ - AAA - aba - (2 rows) + ABA + (1 row) -SELECT name FROM srt WHERE name SIMILAR TO '%A.*'; +SELECT name FROM srt WHERE name SIMILAR TO '%A.*' order by name; name ------ - AAA - aba - (2 rows) + ABA + (1 row) -- Explicit casts. SELECT true::citext = 'true' AS t; diff --cc doc/bug.template index fd6fa6e177,00a6636321..8476502ce5 --- a/doc/bug.template +++ b/doc/bug.template @@@ -27,7 -27,7 +27,7 @@@ System Configuration Operating System (example: Linux 2.4.18) : - PostgreSQL version (example: PostgreSQL 9.5.3): Postgres-XL 9.5r1.2 - PostgreSQL version (example: PostgreSQL 9.5.4): PostgreSQL 9.5.4 ++ PostgreSQL version (example: PostgreSQL 9.5.4): Postgres-XL 9.5r1.2 Compiler used (example: gcc 3.3.5) : diff --cc doc/src/sgml/ddl.sgml index 41bf79e5c2,c1fb08bb22..98df43338e mode 100755,100644..100755 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml diff --cc doc/src/sgml/ref/create_table.sgml index 136d0a10ba,0abe78d37e..08b1cfffff mode 100755,100644..100755 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml diff --cc src/backend/executor/execTuples.c index 4d51277560,6aab933fbc..8e7937cd64 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@@ -89,13 -88,9 +89,12 @@@ #include "nodes/nodeFuncs.h" #include "storage/bufmgr.h" #include "utils/builtins.h" - #include "utils/expandeddatum.h" #include "utils/lsyscache.h" #include "utils/typcache.h" - +#ifdef XCP +#include "pgxc/pgxc.h" +#include "utils/memutils.h" +#endif static TupleDesc ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk); diff --cc src/backend/utils/misc/postgresql.conf.sample index 290afd340d,7e622e37fd..43483caf7d --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@@ -117,12 -115,10 +115,10 @@@ #huge_pages = try # on, off, or try # (change requires restart) #temp_buffers = 8MB # min 800kB -#max_prepared_transactions = 0 # zero disables the feature +#max_prepared_transactions = 10 # zero disables the feature # (change requires restart) - # Note: Increasing max_prepared_transactions costs ~600 bytes of shared memory - # per transaction slot, plus lock space (see max_locks_per_transaction). - # It is not advisable to set max_prepared_transactions nonzero unless you - # actively intend to use prepared transactions. + # Caution: it is not advisable to set max_prepared_transactions nonzero unless + # you actively intend to use prepared transactions. #work_mem = 4MB # min 64kB #maintenance_work_mem = 64MB # min 1MB #autovacuum_work_mem = -1 # min 1MB, or -1 to use maintenance_work_mem diff --cc src/bin/pg_dump/pg_dumpall.c index b0438def18,6ea6be569c..e2114552a1 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@@ -2111,154 -2087,3 +2117,76 @@@ dumpTimestamp(const char *msg if (strftime(buf, sizeof(buf), PGDUMP_STRFTIME_FMT, localtime(&now)) != 0) fprintf(OPF, "-- %s %s\n\n", msg, buf); } + - - /* - * Append the given string to the buffer, with suitable quoting for passing - * the string as a value, in a keyword/pair value in a libpq connection - * string - */ - static void - doConnStrQuoting(PQExpBuffer buf, const char *str) - { - const char *s; - bool needquotes; - - /* - * If the string consists entirely of plain ASCII characters, no need to - * quote it. This is quite conservative, but better safe than sorry. - */ - needquotes = false; - for (s = str; *s; s++) - { - if (!((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || - (*s >= '0' && *s <= '9') || *s == '_' || *s == '.')) - { - needquotes = true; - break; - } - } - - if (needquotes) - { - appendPQExpBufferChar(buf, '\''); - while (*str) - { - /* ' and \ must be escaped by to \' and \\ */ - if (*str == '\'' || *str == '\\') - appendPQExpBufferChar(buf, '\\'); - - appendPQExpBufferChar(buf, *str); - str++; - } - appendPQExpBufferChar(buf, '\''); - } - else - appendPQExpBufferStr(buf, str); - } - - /* - * Append the given string to the shell command being built in the buffer, - * with suitable shell-style quoting. - */ - static void - doShellQuoting(PQExpBuffer buf, const char *str) - { - const char *p; - - #ifndef WIN32 - appendPQExpBufferChar(buf, '\''); - for (p = str; *p; p++) - { - if (*p == '\'') - appendPQExpBufferStr(buf, "'\"'\"'"); - else - appendPQExpBufferChar(buf, *p); - } - appendPQExpBufferChar(buf, '\''); - #else /* WIN32 */ - - appendPQExpBufferChar(buf, '"'); - for (p = str; *p; p++) - { - if (*p == '"') - appendPQExpBufferStr(buf, "\\\""); - else - appendPQExpBufferChar(buf, *p); - } - appendPQExpBufferChar(buf, '"'); - #endif /* WIN32 */ - } - +#ifdef PGXC +static void +dumpNodes(PGconn *conn) +{ + PQExpBuffer query; + PGresult *res; + int num; + int i; + + query = createPQExpBuffer(); + + appendPQExpBuffer(query, "select 'CREATE NODE ' || node_name || '" + " WITH (TYPE = ' || chr(39) || (case when node_type='C'" + " then 'coordinator' else 'datanode' end) || chr(39)" + " || ' , HOST = ' || chr(39) || node_host || chr(39)" + " || ', PORT = ' || node_port || (case when nodeis_primary='t'" + " then ', PRIMARY' else ' ' end) || (case when nodeis_preferred" + " then ', PREFERRED' else ' ' end) || ');' " + " as node_query from pg_catalog.pgxc_node order by oid"); + + res = executeQuery(conn, query->data); + + num = PQntuples(res); + + if (num > 0) + fprintf(OPF, "--\n-- Nodes\n--\n\n"); + + for (i = 0; i < num; i++) + { + fprintf(OPF, "%s\n", PQgetvalue(res, i, PQfnumber(res, "node_query"))); + } + fprintf(OPF, "\n"); + + PQclear(res); + destroyPQExpBuffer(query); +} + +static void +dumpNodeGroups(PGconn *conn) +{ + PQExpBuffer query; + PGresult *res; + int num; + int i; + + query = createPQExpBuffer(); + + appendPQExpBuffer(query, + "select 'CREATE NODE GROUP ' || pgxc_group.group_name" + " || ' WITH(' || string_agg(node_name,',') || ');'" + " as group_query from pg_catalog.pgxc_node, pg_catalog.pgxc_group" + " where pgxc_node.oid = any (pgxc_group.group_members)" + " group by pgxc_group.group_name" + " order by pgxc_group.group_name"); + + res = executeQuery(conn, query->data); + + num = PQntuples(res); + + if (num > 0) + fprintf(OPF, "--\n-- Node groups\n--\n\n"); + + for (i = 0; i < num; i++) + { + fprintf(OPF, "%s\n", PQgetvalue(res, i, PQfnumber(res, "group_query"))); + } + fprintf(OPF, "\n"); + + PQclear(res); + destroyPQExpBuffer(query); +} +#endif diff --cc src/include/pg_config.h.win32 index 426b4dc425,90a7f2e1cb..51e75c961a --- a/src/include/pg_config.h.win32 +++ b/src/include/pg_config.h.win32 @@@ -557,16 -557,16 +557,16 @@@ #define MEMSET_LOOP_LIMIT 1024 /* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT "pgsql-bugs@postgresql.org" +#define PACKAGE_BUGREPORT "postgres-xl-bugs@lists.sourceforge.net" /* Define to the full name of this package. */ -#define PACKAGE_NAME "PostgreSQL" +#define PACKAGE_NAME "Postgres-XL" /* Define to the full name and version of this package. */ -#define PACKAGE_STRING "PostgreSQL 9.5.4" +#define PACKAGE_STRING "Postgres-XL 9.5r1.2" /* Define to the version of this package. */ - #define PACKAGE_VERSION "9.5.3" + #define PACKAGE_VERSION "9.5.4" /* Define to the name of a signed 128-bit integer type. */ #undef PG_INT128_TYPE @@@ -575,19 -575,13 +575,19 @@@ #define PG_INT64_TYPE long long int /* PostgreSQL version as a string */ - #define PG_VERSION "9.5.3" + #define PG_VERSION "9.5.4" /* PostgreSQL version as a number */ - #define PG_VERSION_NUM 90503 + #define PG_VERSION_NUM 90504 /* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "postgresql" +#define PACKAGE_TARNAME "postgres-xl" + +/* Postgres-XC version as a string */ +#define PGXC_VERSION "1.1devel" + +/* Postgres-XC version as a number */ +#define PGXC_VERSION_NUM 10100 /* Define to the name of the default PostgreSQL service principal in Kerberos. (--with-krb-srvnam=NAME) */ diff --cc src/test/regress/expected/case.out index eef7f29196,35b6476e50..8ff5abfa05 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@@ -295,15 -287,54 +295,54 @@@ UPDATE CASE_TB ELSE (3 * j) END FROM CASE2_TBL b WHERE j = -CASE_TBL.i; -SELECT * FROM CASE_TBL; +SELECT * FROM CASE_TBL ORDER BY i, f; i | f -----+------- - 8 | 20.2 - -9 | -30.3 -12 | + -9 | -30.3 -8 | 10.1 + 8 | 20.2 (4 rows) + -- + -- Nested CASE expressions + -- + -- This test exercises a bug caused by aliasing econtext->caseValue_isNull + -- with the isNull argument of the inner CASE's ExecEvalCase() call. After + -- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, + -- the isNull flag for the case test value incorrectly became true, causing + -- the third WHEN-clause not to match. The volatile function calls are needed + -- to prevent constant-folding in the planner, which would hide the bug. + CREATE FUNCTION vol(text) returns text as + 'begin return $1; end' language plpgsql volatile; + SELECT CASE + (CASE vol('bar') + WHEN 'foo' THEN 'it was foo!' + WHEN vol(null) THEN 'null input' + WHEN 'bar' THEN 'it was bar!' END + ) + WHEN 'it was foo!' THEN 'foo recognized' + WHEN 'it was bar!' THEN 'bar recognized' + ELSE 'unrecognized' END; + case + ---------------- + bar recognized + (1 row) + + -- In this case, we can't inline the SQL function without confusing things. + CREATE DOMAIN foodomain AS text; + CREATE FUNCTION volfoo(text) returns foodomain as + 'begin return $1::foodomain; end' language plpgsql volatile; + CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as + 'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql; + CREATE OPERATOR = (procedure = inline_eq, + leftarg = foodomain, rightarg = foodomain); + SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END; + case + ------------ + is not foo + (1 row) + -- -- Clean up -- diff --cc src/test/regress/expected/case_1.out index 3a798f7b5b,0000000000..4fbff97f32 mode 100644,000000..100644 --- a/src/test/regress/expected/case_1.out +++ b/src/test/regress/expected/case_1.out @@@ -1,317 -1,0 +1,361 @@@ +-- +-- CASE +-- Test the case statement +-- +CREATE TABLE CASE_TBL ( + i integer, + f double precision +); +CREATE TABLE CASE2_TBL ( + i integer, + j integer +); +INSERT INTO CASE_TBL VALUES (1, 10.1); +INSERT INTO CASE_TBL VALUES (2, 20.2); +INSERT INTO CASE_TBL VALUES (3, -30.3); +INSERT INTO CASE_TBL VALUES (4, NULL); +INSERT INTO CASE2_TBL VALUES (1, -1); +INSERT INTO CASE2_TBL VALUES (2, -2); +INSERT INTO CASE2_TBL VALUES (3, -3); +INSERT INTO CASE2_TBL VALUES (2, -4); +INSERT INTO CASE2_TBL VALUES (1, NULL); +INSERT INTO CASE2_TBL VALUES (NULL, -6); +-- +-- Simplest examples without tables +-- +SELECT '3' AS "One", + CASE + WHEN 1 < 2 THEN 3 + END AS "Simple WHEN"; + One | Simple WHEN +-----+------------- + 3 | 3 +(1 row) + +SELECT '' AS "One", + CASE + WHEN 1 > 2 THEN 3 + END AS "Simple default"; + One | Simple default +--------+---------------- + | +(1 row) + +SELECT '3' AS "One", + CASE + WHEN 1 < 2 THEN 3 + ELSE 4 + END AS "Simple ELSE"; + One | Simple ELSE +-----+------------- + 3 | 3 +(1 row) + +SELECT '4' AS "One", + CASE + WHEN 1 > 2 THEN 3 + ELSE 4 + END AS "ELSE default"; + One | ELSE default +-----+-------------- + 4 | 4 +(1 row) + +SELECT '6' AS "One", + CASE + WHEN 1 > 2 THEN 3 + WHEN 4 < 5 THEN 6 + ELSE 7 + END AS "Two WHEN with default"; + One | Two WHEN with default +-----+----------------------- + 6 | 6 +(1 row) + +-- Constant-expression folding shouldn't evaluate unreachable subexpressions +SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END; + case +------ + 1 +(1 row) + +SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END; + case +------ + 1 +(1 row) + +-- However we do not currently suppress folding of potentially +-- reachable subexpressions +SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl; +ERROR: division by zero +-- Test for cases involving untyped literals in test expression +SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END; + case +------ + 1 +(1 row) + +-- +-- Examples of targets involving tables +-- +SELECT '' AS "Five", + CASE + WHEN i >= 3 THEN i + END AS ">= 3 or Null" + FROM CASE_TBL + ORDER BY 2; + Five | >= 3 or Null +------+-------------- + | 3 + | 4 + | + | +(4 rows) + +SELECT '' AS "Five", + CASE WHEN i >= 3 THEN (i + i) + ELSE i + END AS "Simplest Math" + FROM CASE_TBL + ORDER BY 2; + Five | Simplest Math +------+--------------- + | 1 + | 2 + | 6 + | 8 +(4 rows) + +SELECT '' AS "Five", i AS "Value", + CASE WHEN (i < 0) THEN 'small' + WHEN (i = 0) THEN 'zero' + WHEN (i = 1) THEN 'one' + WHEN (i = 2) THEN 'two' + ELSE 'big' + END AS "Category" + FROM CASE_TBL + ORDER BY 2, 3; + Five | Value | Category +------+-------+---------- + | 1 | one + | 2 | two + | 3 | big + | 4 | big +(4 rows) + +SELECT '' AS "Five", + CASE WHEN ((i < 0) or (i < 0)) THEN 'small' + WHEN ((i = 0) or (i = 0)) THEN 'zero' + WHEN ((i = 1) or (i = 1)) THEN 'one' + WHEN ((i = 2) or (i = 2)) THEN 'two' + ELSE 'big' + END AS "Category" + FROM CASE_TBL + ORDER BY 2; + Five | Category +------+---------- + | big + | big + | one + | two +(4 rows) + +-- +-- Examples of qualifications involving tables +-- +-- +-- NULLIF() and COALESCE() +-- Shorthand forms for typical CASE constructs +-- defined in the SQL standard. +-- +SELECT * FROM CASE_TBL WHERE COALESCE(f,i) = 4; + i | f +---+--- + 4 | +(1 row) + +SELECT * FROM CASE_TBL WHERE NULLIF(f,i) = 2; + i | f +---+--- +(0 rows) + +SELECT COALESCE(a.f, b.i, b.j) + FROM CASE_TBL a, CASE2_TBL b + ORDER BY coalesce; + coalesce +---------- + -30.3 + -30.3 + -30.3 + -30.3 + -30.3 + -30.3 + -6 + 1 + 1 + 2 + 2 + 3 + 10.1 + 10.1 + 10.1 + 10.1 + 10.1 + 10.1 + 20.2 + 20.2 + 20.2 + 20.2 + 20.2 + 20.2 +(24 rows) + +SELECT * + FROM CASE_TBL a, CASE2_TBL b + WHERE COALESCE(a.f, b.i, b.j) = 2 + ORDER BY a.i, a.f, b.i, b.j; + i | f | i | j +---+---+---+---- + 4 | | 2 | -4 + 4 | | 2 | -2 +(2 rows) + +SELECT '' AS Five, NULLIF(a.i,b.i) AS "NULLIF(a.i,b.i)", + NULLIF(b.i, 4) AS "NULLIF(b.i,4)" + FROM CASE_TBL a, CASE2_TBL b + ORDER BY 2, 3; + five | NULLIF(a.i,b.i) | NULLIF(b.i,4) +------+-----------------+--------------- + | 1 | 2 + | 1 | 2 + | 1 | 3 + | 1 | + | 2 | 1 + | 2 | 1 + | 2 | 3 + | 2 | + | 3 | 1 + | 3 | 1 + | 3 | 2 + | 3 | 2 + | 3 | + | 4 | 1 + | 4 | 1 + | 4 | 2 + | 4 | 2 + | 4 | 3 + | 4 | + | | 1 + | | 1 + | | 2 + | | 2 + | | 3 +(24 rows) + +SELECT '' AS "Two", * + FROM CASE_TBL a, CASE2_TBL b + WHERE COALESCE(f,b.i) = 2 + ORDER BY a.i, a.f, b.i, b.j; + Two | i | f | i | j +-----+---+---+---+---- + | 4 | | 2 | -4 + | 4 | | 2 | -2 +(2 rows) + +-- +-- Examples of updates involving tables +-- +UPDATE CASE_TBL + SET i = CASE WHEN i >= 3 THEN (- i) + ELSE (2 * i) END; +ERROR: could not plan this distributed update +DETAIL: correlated UPDATE or updating distribution column currently not supported in Postgres-XL. +SELECT * FROM CASE_TBL ORDER BY i, f; + i | f +---+------- + 1 | 10.1 + 2 | 20.2 + 3 | -30.3 + 4 | +(4 rows) + +UPDATE CASE_TBL + SET i = CASE WHEN i >= 2 THEN (2 * i) + ELSE (3 * i) END; +ERROR: could not plan this distributed update +DETAIL: correlated UPDATE or updating distribution column currently not supported in Postgres-XL. +SELECT * FROM CASE_TBL ORDER BY i, f; + i | f +---+------- + 1 | 10.1 + 2 | 20.2 + 3 | -30.3 + 4 | +(4 rows) + +UPDATE CASE_TBL + SET i = CASE WHEN b.i >= 2 THEN (2 * j) + ELSE (3 * j) END + FROM CASE2_TBL b + WHERE j = -CASE_TBL.i; +ERROR: could not plan this distributed update +DETAIL: correlated UPDATE or updating distribution column currently not supported in Postgres-XL. +SELECT * FROM CASE_TBL ORDER BY i, f; + i | f +---+------- + 1 | 10.1 + 2 | 20.2 + 3 | -30.3 + 4 | +(4 rows) + ++-- ++-- Nested CASE expressions ++-- ++-- This test exercises a bug caused by aliasing econtext->caseValue_isNull ++-- with the isNull argument of the inner CASE's ExecEvalCase() call. After ++-- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, ++-- the isNull flag for the case test value incorrectly became true, causing ++-- the third WHEN-clause not to match. The volatile function calls are needed ++-- to prevent constant-folding in the planner, which would hide the bug. ++CREATE FUNCTION vol(text) returns text as ++ 'begin return $1; end' language plpgsql volatile; ++SELECT CASE ++ (CASE vol('bar') ++ WHEN 'foo' THEN 'it was foo!' ++ WHEN vol(null) THEN 'null input' ++ WHEN 'bar' THEN 'it was bar!' END ++ ) ++ WHEN 'it was foo!' THEN 'foo recognized' ++ WHEN 'it was bar!' THEN 'bar recognized' ++ ELSE 'unrecognized' END; ++ case ++---------------- ++ bar recognized ++(1 row) ++ ++-- In this case, we can't inline the SQL function without confusing things. ++CREATE DOMAIN foodomain AS text; ++CREATE FUNCTION volfoo(text) returns foodomain as ++ 'begin return $1::foodomain; end' language plpgsql volatile; ++CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as ++ 'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql; ++CREATE OPERATOR = (procedure = inline_eq, ++ leftarg = foodomain, rightarg = foodomain); ++SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END; ++ case ++------------ ++ is not foo ++(1 row) ++ +-- +-- Clean up +-- +DROP TABLE CASE_TBL; +DROP TABLE CASE2_TBL; ++DROP OPERATOR = (foodomain, foodomain); ++DROP FUNCTION inline_eq(foodomain, foodomain); ++DROP FUNCTION volfoo(text); ++DROP DOMAIN foodomain; ++DROP FUNCTION vol(text); diff --cc src/test/regress/expected/insert_conflict.out index 49a9b9fe1e,113b1be307..4d544a1da3 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@@ -479,6 -454,21 +479,21 @@@ ERROR: column excluded.oid does not ex LINE 1: ...values (1) on conflict (key) do update set data = excluded.o... ^ drop table syscolconflicttest; + -- + -- Previous tests all managed to not test any expressions requiring + -- planner preprocessing ... + -- -create table insertconflict (a bigint, b bigint); ++create table insertconflict (a bigint, b bigint) distribute by replication; + create unique index insertconflicti1 on insertconflict(coalesce(a, 0)); + create unique index insertconflicti2 on insertconflict(b) + where coalesce(a, 1) > 0; + insert into insertconflict values (1, 2) + on conflict (coalesce(a, 0)) do nothing; + insert into insertconflict values (1, 2) + on conflict (b) where coalesce(a, 1) > 0 do nothing; + insert into insertconflict values (1, 2) + on conflict (b) where coalesce(a, 1) > 1 do nothing; + drop table insertconflict; -- ****************************************************************** -- * * -- * Test inheritance (example taken from tutorial) * diff --cc src/test/regress/expected/matview.out index 7c655ca8b2,4ab2370029..0992ff6a21 --- a/src/test/regress/expected/matview.out +++ b/src/test/regress/expected/matview.out @@@ -458,28 -453,68 +458,67 @@@ SELECT * FROM boxmv ORDER BY id DROP TABLE boxes CASCADE; NOTICE: drop cascades to materialized view boxmv ---- make sure that column names are handled correctly - CREATE TABLE v (i int, j int); - CREATE MATERIALIZED VIEW mv_v (ii) AS SELECT i, j AS jj FROM v; - ALTER TABLE v RENAME COLUMN i TO x; - INSERT INTO v values (1, 2); - CREATE UNIQUE INDEX mv_v_ii ON mv_v (ii); - REFRESH MATERIALIZED VIEW mv_v; - UPDATE v SET j = 3 WHERE x = 1; - SELECT * FROM v; + CREATE TABLE mvtest_v (i int, j int); + CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error + ERROR: too many column names were specified + CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok + CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok + CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error + ERROR: too many column names were specified + CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok + CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok + ALTER TABLE mvtest_v RENAME COLUMN i TO x; + INSERT INTO mvtest_v values (1, 2); + CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); + REFRESH MATERIALIZED VIEW mvtest_mv_v; + UPDATE mvtest_v SET j = 3 WHERE x = 1; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; ++-- REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; + REFRESH MATERIALIZED VIEW mvtest_mv_v_2; + REFRESH MATERIALIZED VIEW mvtest_mv_v_3; + REFRESH MATERIALIZED VIEW mvtest_mv_v_4; + SELECT * FROM mvtest_v; x | j ---+--- 1 | 3 (1 row) - SELECT * FROM mv_v; + SELECT * FROM mvtest_mv_v; ii | jj ----+---- - 1 | 3 + 1 | 2 (1 row) - DROP TABLE v CASCADE; - NOTICE: drop cascades to materialized view mv_v + SELECT * FROM mvtest_mv_v_2; + ii | j + ----+--- + 1 | 3 + (1 row) + + SELECT * FROM mvtest_mv_v_3; + ii | jj + ----+---- + 1 | 3 + (1 row) + + SELECT * FROM mvtest_mv_v_4; + ii | j + ----+--- + 1 | 3 + (1 row) + + DROP TABLE mvtest_v CASCADE; + NOTICE: drop cascades to 4 other objects + DETAIL: drop cascades to materialized view mvtest_mv_v + drop cascades to materialized view mvtest_mv_v_2 + drop cascades to materialized view mvtest_mv_v_3 + drop cascades to materialized view mvtest_mv_v_4 + -- make sure that create WITH NO DATA does not plan the query (bug #13907) + create materialized view mvtest_error as select 1/0 as x; -- fail + ERROR: division by zero + create materialized view mvtest_error as select 1/0 as x with no data; + refresh materialized view mvtest_error; -- fail here + ERROR: division by zero + drop materialized view mvtest_error; -- make sure that matview rows can be referenced as source rows (bug #9398) CREATE TABLE v AS SELECT generate_series(1,10) AS a; CREATE MATERIALIZED VIEW mv_v AS SELECT a FROM v WHERE a <= 5; diff --cc src/test/regress/expected/plpgsql_1.out index 4f9dea9c25,0000000000..55a288a048 mode 100644,000000..100644 --- a/src/test/regress/expected/plpgsql_1.out +++ b/src/test/regress/expected/plpgsql_1.out @@@ -1,5531 -1,0 +1,5601 @@@ +-- +-- PLPGSQL +-- +-- Scenario: +-- +-- A building with a modern TP cable installation where any +-- of the wall connectors can be used to plug in phones, +-- ethernet interfaces or local office hubs. The backside +-- of the wall connectors is wired to one of several patch- +-- fields in the building. +-- +-- In the patchfields, there are hubs and all the slots +-- representing the wall connectors. In addition there are +-- slots that can represent a phone line from the central +-- phone system. +-- +-- Triggers ensure consistency of the patching information. +-- +-- Functions are used to build up powerful views that let +-- you look behind the wall when looking at a patchfield +-- or into a room. +-- +create table Room ( + roomno char(8), + comment text +); +create unique index Room_rno on Room using btree (roomno bpchar_ops); +create table WSlot ( + slotname char(20), + roomno char(8), + slotlink char(20), + backlink char(20) +); +create unique index WSlot_name on WSlot using btree (slotname bpchar_ops); +create table PField ( + name text, + comment text +) distribute by replication; +create unique index PField_name on PField using btree (name text_ops); +create table PSlot ( + slotname char(20), + pfname text, + slotlink char(20), + backlink char(20) +); +create unique index PSlot_name on PSlot using btree (slotname bpchar_ops); +create table PLine ( + slotname char(20), + phonenumber char(20), + comment text, + backlink char(20) +); +create unique index PLine_name on PLine using btree (slotname bpchar_ops); +create table Hub ( + name char(14), + comment text, + nslots integer +); +create unique index Hub_name on Hub using btree (name bpchar_ops); +create table HSlot ( + slotname char(20), + hubname char(14), + slotno integer, + slotlink char(20) +); +create unique index HSlot_name on HSlot using btree (slotname bpchar_ops); +create index HSlot_hubname on HSlot using btree (hubname bpchar_ops); +create table System ( + name text, + comment text +); +create unique index System_name on System using btree (name text_ops); +create table IFace ( + slotname char(20), + sysname text, + ifname text, + slotlink char(20) +); +create unique index IFace_name on IFace using btree (slotname bpchar_ops); +create table PHone ( + slotname char(20), + comment text, + slotlink char(20) +); +create unique index PHone_name on PHone using btree (slotname bpchar_ops); +-- ************************************************************ +-- * +-- * Trigger procedures and functions for the patchfield +-- * test of PL/pgSQL +-- * +-- ************************************************************ +-- ************************************************************ +-- * AFTER UPDATE on Room +-- * - If room no changes let wall slots follow +-- ************************************************************ +create function tg_room_au() returns trigger as ' +begin + if new.roomno != old.roomno then + update WSlot set roomno = new.roomno where roomno = old.roomno; + end if; + return new; +end; +' language plpgsql; +create trigger tg_room_au after update + on Room for each row execute procedure tg_room_au(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * AFTER DELETE on Room +-- * - delete wall slots in this room +-- ************************************************************ +create function tg_room_ad() returns trigger as ' +begin + delete from WSlot where roomno = old.roomno; + return old; +end; +' language plpgsql; +create trigger tg_room_ad after delete + on Room for each row execute procedure tg_room_ad(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on WSlot +-- * - Check that room exists +-- ************************************************************ +create function tg_wslot_biu() returns trigger as $$ +begin + if count(*) = 0 from Room where roomno = new.roomno then + raise exception 'Room % does not exist', new.roomno; + end if; + return new; +end; +$$ language plpgsql; +create trigger tg_wslot_biu before insert or update + on WSlot for each row execute procedure tg_wslot_biu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * AFTER UPDATE on PField +-- * - Let PSlots of this field follow +-- ************************************************************ +create function tg_pfield_au() returns trigger as ' +begin + if new.name != old.name then + update PSlot set pfname = new.name where pfname = old.name; + end if; + return new; +end; +' language plpgsql; +create trigger tg_pfield_au after update + on PField for each row execute procedure tg_pfield_au(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * AFTER DELETE on PField +-- * - Remove all slots of this patchfield +-- ************************************************************ +create function tg_pfield_ad() returns trigger as ' +begin + delete from PSlot where pfname = old.name; + return old; +end; +' language plpgsql; +create trigger tg_pfield_ad after delete + on PField for each row execute procedure tg_pfield_ad(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on PSlot +-- * - Ensure that our patchfield does exist +-- ************************************************************ +create function tg_pslot_biu() returns trigger as $proc$ +declare + pfrec record; + ps alias for new; +begin + select into pfrec * from PField where name = ps.pfname; + if not found then + raise exception $$Patchfield "%" does not exist$$, ps.pfname; + end if; + return ps; +end; +$proc$ language plpgsql; +create trigger tg_pslot_biu before insert or update + on PSlot for each row execute procedure tg_pslot_biu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * AFTER UPDATE on System +-- * - If system name changes let interfaces follow +-- ************************************************************ +create function tg_system_au() returns trigger as ' +begin + if new.name != old.name then + update IFace set sysname = new.name where sysname = old.name; + end if; + return new; +end; +' language plpgsql; +create trigger tg_system_au after update + on System for each row execute procedure tg_system_au(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on IFace +-- * - set the slotname to IF.sysname.ifname +-- ************************************************************ +create function tg_iface_biu() returns trigger as $$ +declare + sname text; + sysrec record; +begin + select into sysrec * from system where name = new.sysname; + if not found then + raise exception $q$system "%" does not exist$q$, new.sysname; + end if; + sname := 'IF.' || new.sysname; + sname := sname || '.'; + sname := sname || new.ifname; + if length(sname) > 20 then + raise exception 'IFace slotname "%" too long (20 char max)', sname; + end if; + new.slotname := sname; + return new; +end; +$$ language plpgsql; +create trigger tg_iface_biu before insert or update + on IFace for each row execute procedure tg_iface_biu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on Hub +-- * - insert/delete/rename slots as required +-- ************************************************************ +create function tg_hub_a() returns trigger as ' +declare + hname text; + dummy integer; +begin + if tg_op = ''INSERT'' then + dummy := tg_hub_adjustslots(new.name, 0, new.nslots); + return new; + end if; + if tg_op = ''UPDATE'' then + if new.name != old.name then + update HSlot set hubname = new.name where hubname = old.name; + end if; + dummy := tg_hub_adjustslots(new.name, old.nslots, new.nslots); + return new; + end if; + if tg_op = ''DELETE'' then + dummy := tg_hub_adjustslots(old.name, old.nslots, 0); + return old; + end if; +end; +' language plpgsql; +create trigger tg_hub_a after insert or update or delete + on Hub for each row execute procedure tg_hub_a(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * Support function to add/remove slots of Hub +-- ************************************************************ +create function tg_hub_adjustslots(hname bpchar, + oldnslots integer, + newnslots integer) +returns integer as ' +begin + if newnslots = oldnslots then + return 0; + end if; + if newnslots < oldnslots then + delete from HSlot where hubname = hname and slotno > newnslots; + return 0; + end if; + for i in oldnslots + 1 .. newnslots loop + insert into HSlot (slotname, hubname, slotno, slotlink) + values (''HS.dummy'', hname, i, ''''); + end loop; + return 0; +end +' language plpgsql; +-- Test comments +COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS 'function with args'; +ERROR: function tg_hub_adjustslots_wrong(character, integer, integer) does not exist +COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS 'function with args'; +COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS NULL; +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on HSlot +-- * - prevent from manual manipulation +-- * - set the slotname to HS.hubname.slotno +-- ************************************************************ +create function tg_hslot_biu() returns trigger as ' +declare + sname text; + xname HSlot.slotname%TYPE; + hubrec record; +begin + select into hubrec * from Hub where name = new.hubname; + if not found then + raise exception ''no manual manipulation of HSlot''; + end if; + if new.slotno < 1 or new.slotno > hubrec.nslots then + raise exception ''no manual manipulation of HSlot''; + end if; + if tg_op = ''UPDATE'' and new.hubname != old.hubname then + if count(*) > 0 from Hub where name = old.hubname then + raise exception ''no manual manipulation of HSlot''; + end if; + end if; + sname := ''HS.'' || trim(new.hubname); + sname := sname || ''.''; + sname := sname || new.slotno::text; + if length(sname) > 20 then + raise exception ''HSlot slotname "%" too long (20 char max)'', sname; + end if; + new.slotname := sname; + return new; +end; +' language plpgsql; +create trigger tg_hslot_biu before insert or update + on HSlot for each row execute procedure tg_hslot_biu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE DELETE on HSlot +-- * - prevent from manual manipulation +-- ************************************************************ +create function tg_hslot_bd() returns trigger as ' +declare + hubrec record; +begin + select into hubrec * from Hub where name = old.hubname; + if not found then + return old; + end if; + if old.slotno > hubrec.nslots then + return old; + end if; + raise exception ''no manual manipulation of HSlot''; +end; +' language plpgsql; +create trigger tg_hslot_bd before delete + on HSlot for each row execute procedure tg_hslot_bd(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE INSERT on all slots +-- * - Check name prefix +-- ************************************************************ +create function tg_chkslotname() returns trigger as ' +begin + if substr(new.slotname, 1, 2) != tg_argv[0] then + raise exception ''slotname must begin with %'', tg_argv[0]; + end if; + return new; +end; +' language plpgsql; +create trigger tg_chkslotname before insert + on PSlot for each row execute procedure tg_chkslotname('PS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotname before insert + on WSlot for each row execute procedure tg_chkslotname('WS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotname before insert + on PLine for each row execute procedure tg_chkslotname('PL'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotname before insert + on IFace for each row execute procedure tg_chkslotname('IF'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotname before insert + on PHone for each row execute procedure tg_chkslotname('PH'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on all slots with slotlink +-- * - Set slotlink to empty string if NULL value given +-- ************************************************************ +create function tg_chkslotlink() returns trigger as ' +begin + if new.slotlink isnull then + new.slotlink := ''''; + end if; + return new; +end; +' language plpgsql; +create trigger tg_chkslotlink before insert or update + on PSlot for each row execute procedure tg_chkslotlink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotlink before insert or update + on WSlot for each row execute procedure tg_chkslotlink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotlink before insert or update + on IFace for each row execute procedure tg_chkslotlink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotlink before insert or update + on HSlot for each row execute procedure tg_chkslotlink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkslotlink before insert or update + on PHone for each row execute procedure tg_chkslotlink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE INSERT or UPDATE on all slots with backlink +-- * - Set backlink to empty string if NULL value given +-- ************************************************************ +create function tg_chkbacklink() returns trigger as ' +begin + if new.backlink isnull then + new.backlink := ''''; + end if; + return new; +end; +' language plpgsql; +create trigger tg_chkbacklink before insert or update + on PSlot for each row execute procedure tg_chkbacklink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkbacklink before insert or update + on WSlot for each row execute procedure tg_chkbacklink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_chkbacklink before insert or update + on PLine for each row execute procedure tg_chkbacklink(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE UPDATE on PSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_pslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PSlot where slotname = old.slotname; + insert into PSlot ( + slotname, + pfname, + slotlink, + backlink + ) values ( + new.slotname, + new.pfname, + new.slotlink, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_pslot_bu before update + on PSlot for each row execute procedure tg_pslot_bu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE UPDATE on WSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_wslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from WSlot where slotname = old.slotname; + insert into WSlot ( + slotname, + roomno, + slotlink, + backlink + ) values ( + new.slotname, + new.roomno, + new.slotlink, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_wslot_bu before update + on WSlot for each row execute procedure tg_Wslot_bu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE UPDATE on PLine +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_pline_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PLine where slotname = old.slotname; + insert into PLine ( + slotname, + phonenumber, + comment, + backlink + ) values ( + new.slotname, + new.phonenumber, + new.comment, + new.backlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_pline_bu before update + on PLine for each row execute procedure tg_pline_bu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE UPDATE on IFace +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_iface_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from IFace where slotname = old.slotname; + insert into IFace ( + slotname, + sysname, + ifname, + slotlink + ) values ( + new.slotname, + new.sysname, + new.ifname, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_iface_bu before update + on IFace for each row execute procedure tg_iface_bu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE UPDATE on HSlot +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_hslot_bu() returns trigger as ' +begin + if new.slotname != old.slotname or new.hubname != old.hubname then + delete from HSlot where slotname = old.slotname; + insert into HSlot ( + slotname, + hubname, + slotno, + slotlink + ) values ( + new.slotname, + new.hubname, + new.slotno, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_hslot_bu before update + on HSlot for each row execute procedure tg_hslot_bu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * BEFORE UPDATE on PHone +-- * - do delete/insert instead of update if name changes +-- ************************************************************ +create function tg_phone_bu() returns trigger as ' +begin + if new.slotname != old.slotname then + delete from PHone where slotname = old.slotname; + insert into PHone ( + slotname, + comment, + slotlink + ) values ( + new.slotname, + new.comment, + new.slotlink + ); + return null; + end if; + return new; +end; +' language plpgsql; +create trigger tg_phone_bu before update + on PHone for each row execute procedure tg_phone_bu(); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on slot with backlink +-- * - Ensure that the opponent correctly points back to us +-- ************************************************************ +create function tg_backlink_a() returns trigger as ' +declare + dummy integer; +begin + if tg_op = ''INSERT'' then + if new.backlink != '''' then + dummy := tg_backlink_set(new.backlink, new.slotname); + end if; + return new; + end if; + if tg_op = ''UPDATE'' then + if new.backlink != old.backlink then + if old.backlink != '''' then + dummy := tg_backlink_unset(old.backlink, old.slotname); + end if; + if new.backlink != '''' then + dummy := tg_backlink_set(new.backlink, new.slotname); + end if; + else + if new.slotname != old.slotname and new.backlink != '''' then + dummy := tg_slotlink_set(new.backlink, new.slotname); + end if; + end if; + return new; + end if; + if tg_op = ''DELETE'' then + if old.backlink != '''' then + dummy := tg_backlink_unset(old.backlink, old.slotname); + end if; + return old; + end if; +end; +' language plpgsql; +create trigger tg_backlink_a after insert or update or delete + on PSlot for each row execute procedure tg_backlink_a('PS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_backlink_a after insert or update or delete + on WSlot for each row execute procedure tg_backlink_a('WS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_backlink_a after insert or update or delete + on PLine for each row execute procedure tg_backlink_a('PL'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * Support function to set the opponents backlink field +-- * if it does not already point to the requested slot +-- ************************************************************ +create function tg_backlink_set(myname bpchar, blname bpchar) +returns integer as ' +declare + mytype char(2); + link char(4); + rec record; +begin + mytype := substr(myname, 1, 2); + link := mytype || substr(blname, 1, 2); + if link = ''PLPL'' then + raise exception + ''backlink between two phone lines does not make sense''; + end if; + if link in (''PLWS'', ''WSPL'') then + raise exception + ''direct link of phone line to wall slot not permitted''; + end if; + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update PSlot set backlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update WSlot set backlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PL'' then + select into rec * from PLine where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.backlink != blname then + update PLine set backlink = blname where slotname = myname; + end if; + return 0; + end if; + raise exception ''illegal backlink beginning with %'', mytype; +end; +' language plpgsql; +-- ************************************************************ +-- * Support function to clear out the backlink field if +-- * it still points to specific slot +-- ************************************************************ +create function tg_backlink_unset(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + rec record; +begin + mytype := substr(myname, 1, 2); + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update PSlot set backlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update WSlot set backlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PL'' then + select into rec * from PLine where slotname = myname; + if not found then + return 0; + end if; + if rec.backlink = blname then + update PLine set backlink = '''' where slotname = myname; + end if; + return 0; + end if; +end +' language plpgsql; +-- ************************************************************ +-- * AFTER INSERT or UPDATE or DELETE on slot with slotlink +-- * - Ensure that the opponent correctly points back to us +-- ************************************************************ +create function tg_slotlink_a() returns trigger as ' +declare + dummy integer; +begin + if tg_op = ''INSERT'' then + if new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + return new; + end if; + if tg_op = ''UPDATE'' then + if new.slotlink != old.slotlink then + if old.slotlink != '''' then + dummy := tg_slotlink_unset(old.slotlink, old.slotname); + end if; + if new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + else + if new.slotname != old.slotname and new.slotlink != '''' then + dummy := tg_slotlink_set(new.slotlink, new.slotname); + end if; + end if; + return new; + end if; + if tg_op = ''DELETE'' then + if old.slotlink != '''' then + dummy := tg_slotlink_unset(old.slotlink, old.slotname); + end if; + return old; + end if; +end; +' language plpgsql; +create trigger tg_slotlink_a after insert or update or delete + on PSlot for each row execute procedure tg_slotlink_a('PS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_slotlink_a after insert or update or delete + on WSlot for each row execute procedure tg_slotlink_a('WS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_slotlink_a after insert or update or delete + on IFace for each row execute procedure tg_slotlink_a('IF'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_slotlink_a after insert or update or delete + on HSlot for each row execute procedure tg_slotlink_a('HS'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +create trigger tg_slotlink_a after insert or update or delete + on PHone for each row execute procedure tg_slotlink_a('PH'); +ERROR: Postgres-XL does not support TRIGGER yet +DETAIL: The feature is not currently supported +-- ************************************************************ +-- * Support function to set the opponents slotlink field +-- * if it does not already point to the requested slot +-- ************************************************************ +create function tg_slotlink_set(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + link char(4); + rec record; +begin + mytype := substr(myname, 1, 2); + link := mytype || substr(blname, 1, 2); + if link = ''PHPH'' then + raise exception + ''slotlink between two phones does not make sense''; + end if; + if link in (''PHHS'', ''HSPH'') then + raise exception + ''link of phone to hub does not make sense''; + end if; + if link in (''PHIF'', ''IFPH'') then + raise exception + ''link of phone to hub does not make sense''; + end if; + if link in (''PSWS'', ''WSPS'') then + raise exception + ''slotlink from patchslot to wallslot not permitted''; + end if; + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update PSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update WSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''IF'' then + select into rec * from IFace where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update IFace set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''HS'' then + select into rec * from HSlot where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update HSlot set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PH'' then + select into rec * from PHone where slotname = myname; + if not found then + raise exception ''% does not exist'', myname; + end if; + if rec.slotlink != blname then + update PHone set slotlink = blname where slotname = myname; + end if; + return 0; + end if; + raise exception ''illegal slotlink beginning with %'', mytype; +end; +' language plpgsql; +-- ************************************************************ +-- * Support function to clear out the slotlink field if +-- * it still points to specific slot +-- ************************************************************ +create function tg_slotlink_unset(bpchar, bpchar) +returns integer as ' +declare + myname alias for $1; + blname alias for $2; + mytype char(2); + rec record; +begin + mytype := substr(myname, 1, 2); + if mytype = ''PS'' then + select into rec * from PSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update PSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''WS'' then + select into rec * from WSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update WSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''IF'' then + select into rec * from IFace where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update IFace set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''HS'' then + select into rec * from HSlot where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update HSlot set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; + if mytype = ''PH'' then + select into rec * from PHone where slotname = myname; + if not found then + return 0; + end if; + if rec.slotlink = blname then + update PHone set slotlink = '''' where slotname = myname; + end if; + return 0; + end if; +end; +' language plpgsql; +-- ************************************************************ +-- * Describe the backside of a patchfield slot +-- ************************************************************ +create function pslot_backlink_view(bpchar) +returns text as ' +<> +declare + rec record; + bltype char(2); + retval text; +begin + select into rec * from PSlot where slotname = $1; + if not found then + return ''''; + end if; + if rec.backlink = '''' then + return ''-''; + end if; + bltype := substr(rec.backlink, 1, 2); + if bltype = ''PL'' then + declare + rec record; + begin + select into rec * from PLine where slotname = "outer".rec.backlink; + retval := ''Phone line '' || trim(rec.phonenumber); + if rec.comment != '''' then + retval := retval || '' (''; + retval := retval || rec.comment; + retval := retval || '')''; + end if; + return retval; + end; + end if; + if bltype = ''WS'' then + select into rec * from WSlot where slotname = rec.backlink; + retval := trim(rec.slotname) || '' in room ''; + retval := retval || trim(rec.roomno); + retval := retval || '' -> ''; + return retval || wslot_slotlink_view(rec.slotname); + end if; + return rec.backlink; +end; +' language plpgsql; +-- ************************************************************ +-- * Describe the front of a patchfield slot +-- ************************************************************ +create function pslot_slotlink_view(bpchar) +returns text as ' +declare + psrec record; + sltype char(2); + retval text; +begin + select into psrec * from PSlot where slotname = $1; + if not found then + return ''''; + end if; + if psrec.slotlink = '''' then + return ''-''; + end if; + sltype := substr(psrec.slotlink, 1, 2); + if sltype = ''PS'' then + retval := trim(psrec.slotlink) || '' -> ''; + return retval || pslot_backlink_view(psrec.slotlink); + end if; + if sltype = ''HS'' then + retval := comment from Hub H, HSlot HS + where HS.slotname = psrec.slotlink + and H.name = HS.hubname; + retval := retval || '' slot ''; + retval := retval || slotno::text from HSlot + where slotname = psrec.slotlink; + return retval; + end if; + return psrec.slotlink; +end; +' language plpgsql; +-- ************************************************************ +-- * Describe the front of a wall connector slot +-- ************************************************************ +create function wslot_slotlink_view(bpchar) +returns text as ' +declare + rec record; + sltype char(2); + retval text; +begin + select into rec * from WSlot where slotname = $1; + if not found then + return ''''; + end if; + if rec.slotlink = '''' then + return ''-''; + end if; + sltype := substr(rec.slotlink, 1, 2); + if sltype = ''PH'' then + select into rec * from PHone where slotname = rec.slotlink; + retval := ''Phone '' || trim(rec.slotname); + if rec.comment != '''' then + retval := retval || '' (''; + retval := retval || rec.comment; + retval := retval || '')''; + end if; + return retval; + end if; + if sltype = ''IF'' then + declare + syrow System%RowType; + ifrow IFace%ROWTYPE; + begin + select into ifrow * from IFace where slotname = rec.slotlink; + select into syrow * from System where name = ifrow.sysname; + retval := syrow.name || '' IF ''; + retval := retval || ifrow.ifname; + if syrow.comment != '''' then + retval := retval || '' (''; + retval := retval || syrow.comment; + retval := retval || '')''; + end if; + return retval; + end; + end if; + return rec.slotlink; +end; +' language plpgsql; +-- ************************************************************ +-- * View of a patchfield describing backside and patches +-- ************************************************************ +create view Pfield_v1 as select PF.pfname, PF.slotname, + pslot_backlink_view(PF.slotname) as backside, + pslot_slotlink_view(PF.slotname) as patch + from PSlot PF; +-- +-- First we build the house - so we create the rooms +-- +insert into Room values ('001', 'Entrance'); +insert into Room values ('002', 'Office'); +insert into Room values ('003', 'Office'); +insert into Room values ('004', 'Technical'); +insert into Room values ('101', 'Office'); +insert into Room values ('102', 'Conference'); +insert into Room values ('103', 'Restroom'); +insert into Room values ('104', 'Technical'); +insert into Room values ('105', 'Office'); +insert into Room values ('106', 'Office'); +-- +-- Second we install the wall connectors +-- +insert into WSlot values ('WS.001.1a', '001', '', ''); +insert into WSlot values ('WS.001.1b', '001', '', ''); +insert into WSlot values ('WS.001.2a', '001', '', ''); +insert into WSlot values ('WS.001.2b', '001', '', ''); +insert into WSlot values ('WS.001.3a', '001', '', ''); +insert into WSlot values ('WS.001.3b', '001', '', ''); +insert into WSlot values ('WS.002.1a', '002', '', ''); +insert into WSlot values ('WS.002.1b', '002', '', ''); +insert into WSlot values ('WS.002.2a', '002', '', ''); +insert into WSlot values ('WS.002.2b', '002', '', ''); +insert into WSlot values ('WS.002.3a', '002', '', ''); +insert into WSlot values ('WS.002.3b', '002', '', ''); +insert into WSlot values ('WS.003.1a', '003', '', ''); +insert into WSlot values ('WS.003.1b', '003', '', ''); +insert into WSlot values ('WS.003.2a', '003', '', ''); +insert into WSlot values ('WS.003.2b', '003', '', ''); +insert into WSlot values ('WS.003.3a', '003', '', ''); +insert into WSlot values ('WS.003.3b', '003', '', ''); +insert into WSlot values ('WS.101.1a', '101', '', ''); +insert into WSlot values ('WS.101.1b', '101', '', ''); +insert into WSlot values ('WS.101.2a', '101', '', ''); +insert into WSlot values ('WS.101.2b', '101', '', ''); +insert into WSlot values ('WS.101.3a', '101', '', ''); +insert into WSlot values ('WS.101.3b', '101', '', ''); +insert into WSlot values ('WS.102.1a', '102', '', ''); +insert into WSlot values ('WS.102.1b', '102', '', ''); +insert into WSlot values ('WS.102.2a', '102', '', ''); +insert into WSlot values ('WS.102.2b', '102', '', ''); +insert into WSlot values ('WS.102.3a', '102', '', ''); +insert into WSlot values ('WS.102.3b', '102', '', ''); +insert into WSlot values ('WS.105.1a', '105', '', ''); +insert into WSlot values ('WS.105.1b', '105', '', ''); +insert into WSlot values ('WS.105.2a', '105', '', ''); +insert into WSlot values ('WS.105.2b', '105', '', ''); +insert into WSlot values ('WS.105.3a', '105', '', ''); +insert into WSlot values ('WS.105.3b', '105', '', ''); +insert into WSlot values ('WS.106.1a', '106', '', ''); +insert into WSlot values ('WS.106.1b', '106', '', ''); +insert into WSlot values ('WS.106.2a', '106', '', ''); +insert into WSlot values ('WS.106.2b', '106', '', ''); +insert into WSlot values ('WS.106.3a', '106', '', ''); +insert into WSlot values ('WS.106.3b', '106', '', ''); +-- +-- Now create the patch fields and their slots +-- +insert into PField values ('PF0_1', 'Wallslots basement'); +-- +-- The cables for these will be made later, so they are unconnected for now +-- +insert into PSlot values ('PS.base.a1', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a2', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a3', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a4', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a5', 'PF0_1', '', ''); +insert into PSlot values ('PS.base.a6', 'PF0_1', '', ''); +-- +-- These are already wired to the wall connectors +-- +insert into PSlot values ('PS.base.b1', 'PF0_1', '', 'WS.002.1a'); +insert into PSlot values ('PS.base.b2', 'PF0_1', '', 'WS.002.1b'); +insert into PSlot values ('PS.base.b3', 'PF0_1', '', 'WS.002.2a'); +insert into PSlot values ('PS.base.b4', 'PF0_1', '', 'WS.002.2b'); +insert into PSlot values ('PS.base.b5', 'PF0_1', '', 'WS.002.3a'); +insert into PSlot values ('PS.base.b6', 'PF0_1', '', 'WS.002.3b'); +insert into PSlot values ('PS.base.c1', 'PF0_1', '', 'WS.003.1a'); +insert into PSlot values ('PS.base.c2', 'PF0_1', '', 'WS.003.1b'); +insert into PSlot values ('PS.base.c3', 'PF0_1', '', 'WS.003.2a'); +insert into PSlot values ('PS.base.c4', 'PF0_1', '', 'WS.003.2b'); +insert into PSlot values ('PS.base.c5', 'PF0_1', '', 'WS.003.3a'); +insert into PSlot values ('PS.base.c6', 'PF0_1', '', 'WS.003.3b'); +-- +-- This patchfield will be renamed later into PF0_2 - so its +-- slots references in pfname should follow +-- +insert into PField values ('PF0_X', 'Phonelines basement'); +insert into PSlot values ('PS.base.ta1', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta2', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta3', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta4', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta5', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.ta6', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb1', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb2', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb3', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb4', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb5', 'PF0_X', '', ''); +insert into PSlot values ('PS.base.tb6', 'PF0_X', '', ''); +insert into PField values ('PF1_1', 'Wallslots first floor'); +insert into PSlot values ('PS.first.a1', 'PF1_1', '', 'WS.101.1a'); +insert into PSlot values ('PS.first.a2', 'PF1_1', '', 'WS.101.1b'); +insert into PSlot values ('PS.first.a3', 'PF1_1', '', 'WS.101.2a'); +insert into PSlot values ('PS.first.a4', 'PF1_1', '', 'WS.101.2b'); +insert into PSlot values ('PS.first.a5', 'PF1_1', '', 'WS.101.3a'); +insert into PSlot values ('PS.first.a6', 'PF1_1', '', 'WS.101.3b'); +insert into PSlot values ('PS.first.b1', 'PF1_1', '', 'WS.102.1a'); +insert into PSlot values ('PS.first.b2', 'PF1_1', '', 'WS.102.1b'); +insert into PSlot values ('PS.first.b3', 'PF1_1', '', 'WS.102.2a'); +insert into PSlot values ('PS.first.b4', 'PF1_1', '', 'WS.102.2b'); +insert into PSlot values ('PS.first.b5', 'PF1_1', '', 'WS.102.3a'); +insert into PSlot values ('PS.first.b6', 'PF1_1', '', 'WS.102.3b'); +insert into PSlot values ('PS.first.c1', 'PF1_1', '', 'WS.105.1a'); +insert into PSlot values ('PS.first.c2', 'PF1_1', '', 'WS.105.1b'); +insert into PSlot values ('PS.first.c3', 'PF1_1', '', 'WS.105.2a'); +insert into PSlot values ('PS.first.c4', 'PF1_1', '', 'WS.105.2b'); +insert into PSlot values ('PS.first.c5', 'PF1_1', '', 'WS.105.3a'); +insert into PSlot values ('PS.first.c6', 'PF1_1', '', 'WS.105.3b'); +insert into PSlot values ('PS.first.d1', 'PF1_1', '', 'WS.106.1a'); +insert into PSlot values ('PS.first.d2', 'PF1_1', '', 'WS.106.1b'); +insert into PSlot values ('PS.first.d3', 'PF1_1', '', 'WS.106.2a'); +insert into PSlot values ('PS.first.d4', 'PF1_1', '', 'WS.106.2b'); +insert into PSlot values ('PS.first.d5', 'PF1_1', '', 'WS.106.3a'); +insert into PSlot values ('PS.first.d6', 'PF1_1', '', 'WS.106.3b'); +-- +-- Now we wire the wall connectors 1a-2a in room 001 to the +-- patchfield. In the second update we make an error, and +-- correct it after +-- +update PSlot set backlink = 'WS.001.1a' where slotname = 'PS.base.a1'; +update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a3'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | + WS.001.3a | 001 | | + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | + PS.base.a3 | PF0_1 | | WS.001.1b + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +update PSlot set backlink = 'WS.001.2a' where slotname = 'PS.base.a3'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | + WS.001.3a | 001 | | + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +update PSlot set backlink = 'WS.001.1b' where slotname = 'PS.base.a2'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | + WS.001.3a | 001 | | + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +-- +-- Same procedure for 2b-3b but this time updating the WSlot instead +-- of the PSlot. Due to the triggers the result is the same: +-- WSlot and corresponding PSlot point to each other. +-- +update WSlot set backlink = 'PS.base.a4' where slotname = 'WS.001.2b'; +update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3a'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a6 + WS.001.3b | 001 | | +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +update WSlot set backlink = 'PS.base.a6' where slotname = 'WS.001.3b'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a6 + WS.001.3b | 001 | | PS.base.a6 +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +update WSlot set backlink = 'PS.base.a5' where slotname = 'WS.001.3a'; +select * from WSlot where roomno = '001' order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a5 + WS.001.3b | 001 | | PS.base.a6 +(6 rows) + +select * from PSlot where slotname ~ 'PS.base.a' order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | +(6 rows) + +insert into PField values ('PF1_2', 'Phonelines first floor'); +insert into PSlot values ('PS.first.ta1', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta2', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta3', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta4', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta5', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.ta6', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb1', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb2', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb3', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb4', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb5', 'PF1_2', '', ''); +insert into PSlot values ('PS.first.tb6', 'PF1_2', '', ''); +-- +-- Fix the wrong name for patchfield PF0_2 +-- +update PField set name = 'PF0_2' where name = 'PF0_X'; +select * from PSlot order by slotname; + slotname | pfname | slotlink | backlink +----------------------+--------+----------------------+---------------------- + PS.base.a1 | PF0_1 | | WS.001.1a + PS.base.a2 | PF0_1 | | WS.001.1b + PS.base.a3 | PF0_1 | | WS.001.2a + PS.base.a4 | PF0_1 | | + PS.base.a5 | PF0_1 | | + PS.base.a6 | PF0_1 | | + PS.base.b1 | PF0_1 | | WS.002.1a + PS.base.b2 | PF0_1 | | WS.002.1b + PS.base.b3 | PF0_1 | | WS.002.2a + PS.base.b4 | PF0_1 | | WS.002.2b + PS.base.b5 | PF0_1 | | WS.002.3a + PS.base.b6 | PF0_1 | | WS.002.3b + PS.base.c1 | PF0_1 | | WS.003.1a + PS.base.c2 | PF0_1 | | WS.003.1b + PS.base.c3 | PF0_1 | | WS.003.2a + PS.base.c4 | PF0_1 | | WS.003.2b + PS.base.c5 | PF0_1 | | WS.003.3a + PS.base.c6 | PF0_1 | | WS.003.3b + PS.base.ta1 | PF0_X | | + PS.base.ta2 | PF0_X | | + PS.base.ta3 | PF0_X | | + PS.base.ta4 | PF0_X | | + PS.base.ta5 | PF0_X | | + PS.base.ta6 | PF0_X | | + PS.base.tb1 | PF0_X | | + PS.base.tb2 | PF0_X | | + PS.base.tb3 | PF0_X | | + PS.base.tb4 | PF0_X | | + PS.base.tb5 | PF0_X | | + PS.base.tb6 | PF0_X | | + PS.first.a1 | PF1_1 | | WS.101.1a + PS.first.a2 | PF1_1 | | WS.101.1b + PS.first.a3 | PF1_1 | | WS.101.2a + PS.first.a4 | PF1_1 | | WS.101.2b + PS.first.a5 | PF1_1 | | WS.101.3a + PS.first.a6 | PF1_1 | | WS.101.3b + PS.first.b1 | PF1_1 | | WS.102.1a + PS.first.b2 | PF1_1 | | WS.102.1b + PS.first.b3 | PF1_1 | | WS.102.2a + PS.first.b4 | PF1_1 | | WS.102.2b + PS.first.b5 | PF1_1 | | WS.102.3a + PS.first.b6 | PF1_1 | | WS.102.3b + PS.first.c1 | PF1_1 | | WS.105.1a + PS.first.c2 | PF1_1 | | WS.105.1b + PS.first.c3 | PF1_1 | | WS.105.2a + PS.first.c4 | PF1_1 | | WS.105.2b + PS.first.c5 | PF1_1 | | WS.105.3a + PS.first.c6 | PF1_1 | | WS.105.3b + PS.first.d1 | PF1_1 | | WS.106.1a + PS.first.d2 | PF1_1 | | WS.106.1b + PS.first.d3 | PF1_1 | | WS.106.2a + PS.first.d4 | PF1_1 | | WS.106.2b + PS.first.d5 | PF1_1 | | WS.106.3a + PS.first.d6 | PF1_1 | | WS.106.3b + PS.first.ta1 | PF1_2 | | + PS.first.ta2 | PF1_2 | | + PS.first.ta3 | PF1_2 | | + PS.first.ta4 | PF1_2 | | + PS.first.ta5 | PF1_2 | | + PS.first.ta6 | PF1_2 | | + PS.first.tb1 | PF1_2 | | + PS.first.tb2 | PF1_2 | | + PS.first.tb3 | PF1_2 | | + PS.first.tb4 | PF1_2 | | + PS.first.tb5 | PF1_2 | | + PS.first.tb6 | PF1_2 | | +(66 rows) + +select * from WSlot order by slotname; + slotname | roomno | slotlink | backlink +----------------------+----------+----------------------+---------------------- + WS.001.1a | 001 | | + WS.001.1b | 001 | | + WS.001.2a | 001 | | + WS.001.2b | 001 | | PS.base.a4 + WS.001.3a | 001 | | PS.base.a5 + WS.001.3b | 001 | | PS.base.a6 + WS.002.1a | 002 | | + WS.002.1b | 002 | | + WS.002.2a | 002 | | + WS.002.2b | 002 | | + WS.002.3a | 002 | | + WS.002.3b | 002 | | + WS.003.1a | 003 | | + WS.003.1b | 003 | | + WS.003.2a | 003 | | + WS.003.2b | 003 | | + WS.003.3a | 003 | | + WS.003.3b | 003 | | + WS.101.1a | 101 | | + WS.101.1b | 101 | | + WS.101.2a | 101 | | + WS.101.2b | 101 | | + WS.101.3a | 101 | | + WS.101.3b | 101 | | + WS.102.1a | 102 | | + WS.102.1b | 102 | | + WS.102.2a | 102 | | + WS.102.2b | 102 | | + WS.102.3a | 102 | | + WS.102.3b | 102 | | + WS.105.1a | 105 | | + WS.105.1b | 105 | | + WS.105.2a | 105 | | + WS.105.2b | 105 | | + WS.105.3a | 105 | | + WS.105.3b | 105 | | + WS.106.1a | 106 | | + WS.106.1b | 106 | | + WS.106.2a | 106 | | + WS.106.2b | 106 | | + WS.106.3a | 106 | | + WS.106.3b | 106 | | +(42 rows) + +-- +-- Install the central phone system and create the phone numbers. +-- They are weired on insert to the patchfields. Again the +-- triggers automatically tell the PSlots to update their +-- backlink field. +-- +insert into PLine values ('PL.001', '-0', 'Central call', 'PS.base.ta1'); +insert into PLine values ('PL.002', '-101', '', 'PS.base.ta2'); +insert into PLine values ('PL.003', '-102', '', 'PS.base.ta3'); +insert into PLine values ('PL.004', '-103', '', 'PS.base.ta5'); +insert into PLine values ('PL.005', '-104', '', 'PS.base.ta6'); +insert into PLine values ('PL.006', '-106', '', 'PS.base.tb2'); +insert into PLine values ('PL.007', '-108', '', 'PS.base.tb3'); +insert into PLine values ('PL.008', '-109', '', 'PS.base.tb4'); +insert into PLine values ('PL.009', '-121', '', 'PS.base.tb5'); +insert into PLine values ('PL.010', '-122', '', 'PS.base.tb6'); +insert into PLine values ('PL.015', '-134', '', 'PS.first.ta1'); +insert into PLine values ('PL.016', '-137', '', 'PS.first.ta3'); +insert into PLine values ('PL.017', '-139', '', 'PS.first.ta4'); +insert into PLine values ('PL.018', '-362', '', 'PS.first.tb1'); +insert into PLine values ('PL.019', '-363', '', 'PS.first.tb2'); +insert into PLine values ('PL.020', '-364', '', 'PS.first.tb3'); +insert into PLine values ('PL.021', '-365', '', 'PS.first.tb5'); +insert into PLine values ('PL.022', '-367', '', 'PS.first.tb6'); +insert into PLine values ('PL.028', '-501', 'Fax entrance', 'PS.base.ta2'); +insert into PLine values ('PL.029', '-502', 'Fax first floor', 'PS.first.ta1'); +-- +-- Buy some phones, plug them into the wall and patch the +-- phone lines to the corresponding patchfield slots. +-- +insert into PHone values ('PH.hc001', 'Hicom standard', 'WS.001.1a'); +update PSlot set slotlink = 'PS.base.ta1' where slotname = 'PS.base.a1'; +insert into PHone values ('PH.hc002', 'Hicom standard', 'WS.002.1a'); +update PSlot set slotlink = 'PS.base.ta5' where slotname = 'PS.base.b1'; +insert into PHone values ('PH.hc003', 'Hicom standard', 'WS.002.2a'); +update PSlot set slotlink = 'PS.base.tb2' where slotname = 'PS.base.b3'; +insert into PHone values ('PH.fax001', 'Canon fax', 'WS.001.2a'); +update PSlot set slotlink = 'PS.base.ta2' where slotname = 'PS.base.a3'; +-- +-- Install a hub at one of the patchfields, plug a computers +-- ethernet interface into the wall and patch it to the hub. +-- +insert into Hub values ('base.hub1', 'Patchfield PF0_1 hub', 16); +insert into System values ('orion', 'PC'); +insert into IFace values ('IF', 'orion', 'eth0', 'WS.002.1b'); +update PSlot set slotlink = 'HS.base.hub1.1' where slotname = 'PS.base.b2'; +-- +-- Now we take a look at the patchfield +-- +-- PGXCTODO: This is failing due to issue 3522907, complicated SELECT queries in plpgsql functions +select * from PField_v1 where pfname = 'PF0_1' order by slotname; + pfname | slotname | backside | patch +--------+----------------------+----------------------------+----------------- + PF0_1 | PS.base.a1 | WS.001.1a in room 001 -> - | PS.base.ta1 -> + PF0_1 | PS.base.a2 | | - + PF0_1 | PS.base.a3 | WS.001.2a in room 001 -> - | PS.base.ta2 -> + PF0_1 | PS.base.a4 | - | - + PF0_1 | PS.base.a5 | - | - + PF0_1 | PS.base.a6 | - | - + PF0_1 | PS.base.b1 | | PS.base.ta5 -> + PF0_1 | PS.base.b2 | | + PF0_1 | PS.base.b3 | | PS.base.tb2 -> + PF0_1 | PS.base.b4 | | - + PF0_1 | PS.base.b5 | WS.002.3a in room 002 -> - | - + PF0_1 | PS.base.b6 | | - + PF0_1 | PS.base.c1 | WS.003.1a in room 003 -> - | - + PF0_1 | PS.base.c2 | WS.003.1b in room 003 -> - | - + PF0_1 | PS.base.c3 | WS.003.2a in room 003 -> - | - + PF0_1 | PS.base.c4 | | - + PF0_1 | PS.base.c5 | WS.003.3a in room 003 -> - | - + PF0_1 | PS.base.c6 | | - +(18 rows) + +select * from PField_v1 where pfname = 'PF0_2' order by slotname; + pfname | slotname | backside | patch +--------+----------+----------+------- +(0 rows) + +-- +-- Finally we want errors +-- +insert into PField values ('PF1_1', 'should fail due to unique index'); +ERROR: duplicate key value violates unique constraint "pfield_name" +DETAIL: Key (name)=(PF1_1) already exists. +update PSlot set backlink = 'WS.not.there' where slotname = 'PS.base.a1'; +update PSlot set backlink = 'XX.illegal' where slotname = 'PS.base.a1'; +update PSlot set slotlink = 'PS.not.there' where slotname = 'PS.base.a1'; +update PSlot set slotlink = 'XX.illegal' where slotname = 'PS.base.a1'; +insert into HSlot values ('HS', 'base.hub1', 1, ''); +insert into HSlot values ('HS', 'base.hub1', 20, ''); +ERROR: duplicate key value violates unique constraint "hslot_name" +DETAIL: Key (slotname)=(HS ) already exists. +delete from HSlot; +insert into IFace values ('IF', 'notthere', 'eth0', ''); +ERROR: duplicate key value violates unique constraint "iface_name" +DETAIL: Key (slotname)=(IF ) already exists. +insert into IFace values ('IF', 'orion', 'ethernet_interface_name_too_long', ''); +ERROR: duplicate key value violates unique constraint "iface_name" +DETAIL: Key (slotname)=(IF ) already exists. +-- +-- The following tests are unrelated to the scenario outlined above; +-- they merely exercise specific parts of PL/pgSQL +-- +-- +-- Test recursion, per bug report 7-Sep-01 +-- +CREATE FUNCTION recursion_test(int,int) RETURNS text AS ' +DECLARE rslt text; +BEGIN + IF $1 <= 0 THEN + rslt = CAST($2 AS TEXT); + ELSE + rslt = CAST($1 AS TEXT) || '','' || recursion_test($1 - 1, $2); + END IF; + RETURN rslt; +END;' LANGUAGE plpgsql; +SELECT recursion_test(4,3); + recursion_test +---------------- + 4,3,2,1,3 +(1 row) + +-- +-- Test the FOUND magic variable +-- +CREATE TABLE found_test_tbl (a int) distribute by roundrobin; +create function test_found() + returns boolean as ' + declare + begin + insert into found_test_tbl values (1); + if FOUND then + insert into found_test_tbl values (2); + end if; + + update found_test_tbl set a = 100 where a = 1; + if FOUND then + insert into found_test_tbl values (3); + end if; + + delete from found_test_tbl where a = 9999; -- matches no rows + if not FOUND then + insert into found_test_tbl values (4); + end if; + + for i in 1 .. 10 loop + -- no need to do anything + end loop; + if FOUND then + insert into found_test_tbl values (5); + end if; + + -- never executes the loop + for i in 2 .. 1 loop + -- no need to do anything + end loop; + if not FOUND then + insert into found_test_tbl values (6); + end if; + return true; + end;' language plpgsql; +select test_found(); + test_found +------------ + t +(1 row) + +select * from found_test_tbl order by 1; + a +----- + 2 + 3 + 4 + 5 + 6 + 100 +(6 rows) + +-- +-- Test set-returning functions for PL/pgSQL +-- +create function test_table_func_rec() returns setof found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language plpgsql; +select * from test_table_func_rec() order by 1; + a +----- + 2 + 3 + 4 + 5 + 6 + 100 +(6 rows) + +create function test_table_func_row() returns setof found_test_tbl as ' +DECLARE + row found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; +select * from test_table_func_row() order by 1; + a +----- + 2 + 3 + 4 + 5 + 6 + 100 +(6 rows) + +create function test_ret_set_scalar(int,int) returns setof int as ' +DECLARE + i int; +BEGIN + FOR i IN $1 .. $2 LOOP + RETURN NEXT i + 1; + END LOOP; + RETURN; +END;' language plpgsql; +select * from test_ret_set_scalar(1,10) order by 1; + test_ret_set_scalar +--------------------- + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 +(10 rows) + +create function test_ret_set_rec_dyn(int) returns setof record as ' +DECLARE + retval RECORD; +BEGIN + IF $1 > 10 THEN + SELECT INTO retval 5, 10, 15; + RETURN NEXT retval; + RETURN NEXT retval; + ELSE + SELECT INTO retval 50, 5::numeric, ''xxx''::text; + RETURN NEXT retval; + RETURN NEXT retval; + END IF; + RETURN; +END;' language plpgsql; +SELECT * FROM test_ret_set_rec_dyn(1500) AS (a int, b int, c int) order by a, b, c; + a | b | c +---+----+---- + 5 | 10 | 15 + 5 | 10 | 15 +(2 rows) + +SELECT * FROM test_ret_set_rec_dyn(5) AS (a int, b numeric, c text) order by a, b, c; + a | b | c +----+---+----- + 50 | 5 | xxx + 50 | 5 | xxx +(2 rows) + +create function test_ret_rec_dyn(int) returns record as ' +DECLARE + retval RECORD; +BEGIN + IF $1 > 10 THEN + SELECT INTO retval 5, 10, 15; + RETURN retval; + ELSE + SELECT INTO retval 50, 5::numeric, ''xxx''::text; + RETURN retval; + END IF; +END;' language plpgsql; +SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int) order by a, b, c; + a | b | c +---+----+---- + 5 | 10 | 15 +(1 row) + +SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text) order by a, b, c; + a | b | c +----+---+----- + 50 | 5 | xxx +(1 row) + +-- +-- Test handling of OUT parameters, including polymorphic cases. +-- Note that RETURN is optional with OUT params; we try both ways. +-- +-- wrong way to do it: +create function f1(in i int, out j int) returns int as $$ +begin + return i+1; +end$$ language plpgsql; +ERROR: RETURN cannot have a parameter in function with OUT parameters +LINE 3: return i+1; + ^ +create function f1(in i int, out j int) as $$ +begin + j := i+1; + return; +end$$ language plpgsql; +select f1(42); + f1 +---- + 43 +(1 row) + +select * from f1(42); + j +---- + 43 +(1 row) + +create or replace function f1(inout i int) as $$ +begin + i := i+1; +end$$ language plpgsql; +select f1(42); + f1 +---- + 43 +(1 row) + +select * from f1(42); + i +---- + 43 +(1 row) + +drop function f1(int); +create function f1(in i int, out j int) returns setof int as $$ +begin + j := i+1; + return next; + j := i+2; + return next; + return; +end$$ language plpgsql; +select * from f1(42) order by 1; + j +---- + 43 + 44 +(2 rows) + +drop function f1(int); +create function f1(in i int, out j int, out k text) as $$ +begin + j := i; + j := j+1; + k := 'foo'; +end$$ language plpgsql; +select f1(42); + f1 +---------- + (43,foo) +(1 row) + +select * from f1(42); + j | k +----+----- + 43 | foo +(1 row) + +drop function f1(int); +create function f1(in i int, out j int, out k text) returns setof record as $$ +begin + j := i+1; + k := 'foo'; + return next; + j := j+1; + k := 'foot'; + return next; +end$$ language plpgsql; +select * from f1(42) order by j, k;; + j | k +----+------ + 43 | foo + 44 | foot +(2 rows) + +drop function f1(int); +create function duplic(in i anyelement, out j anyelement, out k anyarray) as $$ +begin + j := i; + k := array[j,j]; + return; +end$$ language plpgsql; +select * from duplic(42); + j | k +----+--------- + 42 | {42,42} +(1 row) + +select * from duplic('foo'::text); + j | k +-----+----------- + foo | {foo,foo} +(1 row) + +drop function duplic(anyelement); +-- +-- test PERFORM +-- +create table perform_test ( + a INT, + b INT +); +create function simple_func(int) returns boolean as ' +BEGIN + IF $1 < 20 THEN + INSERT INTO perform_test VALUES ($1, $1 + 10); + RETURN TRUE; + ELSE + RETURN FALSE; + END IF; +END;' language plpgsql; +create function perform_test_func() returns void as ' +BEGIN + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + PERFORM simple_func(5); + + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + PERFORM simple_func(50); + + IF FOUND then + INSERT INTO perform_test VALUES (100, 100); + END IF; + + RETURN; +END;' language plpgsql; +SELECT perform_test_func(); + perform_test_func +------------------- + +(1 row) + +SELECT * FROM perform_test order by a, b; + a | b +-----+----- + 5 | 15 + 100 | 100 + 100 | 100 +(3 rows) + +drop table perform_test; +-- +-- Test error trapping +-- +create function trap_zero_divide(int) returns int as $$ +declare x int; + sx smallint; +begin + begin -- start a subtransaction + raise notice 'should see this'; + x := 100 / $1; + raise notice 'should see this only if % <> 0', $1; + sx := $1; + raise notice 'should see this only if % fits in smallint', $1; + if $1 < 0 then + raise exception '% is less than zero', $1; + end if; + exception + when division_by_zero then + raise notice 'caught division_by_zero'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE then + raise notice 'caught numeric_value_out_of_range'; + x := -2; + end; + return x; +end$$ language plpgsql; +select trap_zero_divide(50); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_zero_divide(integer) line 5 during statement block entry +select trap_zero_divide(0); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_zero_divide(integer) line 5 during statement block entry +select trap_zero_divide(100000); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_zero_divide(integer) line 5 during statement block entry +select trap_zero_divide(-100); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_zero_divide(integer) line 5 during statement block entry +create function trap_matching_test(int) returns int as $$ +declare x int; + sx smallint; + y int; +begin + begin -- start a subtransaction + x := 100 / $1; + sx := $1; + select into y unique1 from tenk1 where unique2 = + (select unique2 from tenk1 b where ten = $1); + exception + when data_exception then -- category match + raise notice 'caught data_exception'; + x := -1; + when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then + raise notice 'caught numeric_value_out_of_range or cardinality_violation'; + x := -2; + end; + return x; +end$$ language plpgsql; +-- PGXCTODO: This is failing due to issue 3522907, complicated SELECT queries in plpgsql functions +select trap_matching_test(50); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_matching_test(integer) line 6 during statement block entry +select trap_matching_test(0); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_matching_test(integer) line 6 during statement block entry +select trap_matching_test(100000); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_matching_test(integer) line 6 during statement block entry +-- PGXCTODO: This is failing due to issue 3522907, complicated SELECT queries in plpgsql functions +select trap_matching_test(1); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_matching_test(integer) line 6 during statement block entry +create temp table foo (f1 int); +create function subxact_rollback_semantics() returns int as $$ +declare x int; +begin + x := 1; + insert into foo values(x); + begin + x := x + 1; + insert into foo values(x); + raise exception 'inner'; + exception + when others then + x := x * 10; + end; + insert into foo values(x); + return x; +end$$ language plpgsql; +select subxact_rollback_semantics(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function subxact_rollback_semantics() line 6 during statement block entry +select * from foo; + f1 +---- + 1 +(1 row) + +drop table foo; +create function trap_timeout() returns void as $$ +begin + declare x int; + begin + -- we assume this will take longer than 2 seconds: + select count(*) into x from tenk1 a, tenk1 b, tenk1 c; + exception + when others then + raise notice 'caught others?'; + when query_canceled then + raise notice 'nyeah nyeah, can''t stop me'; + end; + -- Abort transaction to abandon the statement_timeout setting. Otherwise, + -- the next top-level statement would be vulnerable to the timeout. + raise exception 'end of function'; +end$$ language plpgsql; +begin; +set statement_timeout to 2000; +select trap_timeout(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_timeout() line 4 during statement block entry +rollback; +-- Test for pass-by-ref values being stored in proper context +create function test_variable_storage() returns text as $$ +declare x text; +begin + x := '1234'; + begin + x := x || '5678'; + -- force error inside subtransaction SPI context + perform trap_zero_divide(-100); + exception + when others then + x := x || '9012'; + end; + return x; +end$$ language plpgsql; +select test_variable_storage(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function test_variable_storage() line 5 during statement block entry +-- +-- test foreign key error trapping +-- +create temp table master(f1 int primary key); +create temp table slave(f1 int references master deferrable); +insert into master values(1); +insert into slave values(1); +insert into slave values(2); -- fails +ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey" +DETAIL: Key (f1)=(2) is not present in table "master". +create function trap_foreign_key(int) returns int as $$ +begin + begin -- start a subtransaction + insert into slave values($1); + exception + when foreign_key_violation then + raise notice 'caught foreign_key_violation'; + return 0; + end; + return 1; +end$$ language plpgsql; +create function trap_foreign_key_2() returns int as $$ +begin + begin -- start a subtransaction + set constraints all immediate; + exception + when foreign_key_violation then + raise notice 'caught foreign_key_violation'; + return 0; + end; + return 1; +end$$ language plpgsql; +select trap_foreign_key(1); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_foreign_key(integer) line 3 during statement block entry +select trap_foreign_key(2); -- detects FK violation +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_foreign_key(integer) line 3 during statement block entry +begin; + set constraints all deferred; + select trap_foreign_key(2); -- should not detect FK violation +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function trap_foreign_key(integer) line 3 during statement block entry + savepoint x; +ERROR: current transaction is aborted, commands ignored until end of transaction block + set constraints all immediate; -- fails +ERROR: current transaction is aborted, commands ignored until end of transaction block + rollback to x; +ERROR: no such savepoint + select trap_foreign_key_2(); -- detects FK violation +ERROR: current transaction is aborted, commands ignored until end of transaction block +commit; -- still fails +drop function trap_foreign_key(int); +drop function trap_foreign_key_2(); +-- +-- Test proper snapshot handling in simple expressions +-- +create temp table users(login text, id serial); +create function sp_id_user(a_login text) returns int as $$ +declare x int; +begin + select into x id from users where login = a_login; + if found then return x; end if; + return 0; +end$$ language plpgsql stable; +insert into users values('user1'); +select sp_id_user('user1'); + sp_id_user +------------ + 1 +(1 row) + +select sp_id_user('userx'); + sp_id_user +------------ + 0 +(1 row) + +create function sp_add_user(a_login text) returns int as $$ +declare my_id_user int; +begin + my_id_user = sp_id_user( a_login ); + IF my_id_user > 0 THEN + RETURN -1; -- error code for existing user + END IF; + INSERT INTO users ( login ) VALUES ( a_login ); + my_id_user = sp_id_user( a_login ); + IF my_id_user = 0 THEN + RETURN -2; -- error code for insertion failure + END IF; + RETURN my_id_user; +end$$ language plpgsql; +select sp_add_user('user1'); + sp_add_user +------------- + -1 +(1 row) + +select sp_add_user('user2'); + sp_add_user +------------- + 2 +(1 row) + +select sp_add_user('user2'); + sp_add_user +------------- + -1 +(1 row) + +select sp_add_user('user3'); + sp_add_user +------------- + 3 +(1 row) + +select sp_add_user('user3'); + sp_add_user +------------- + -1 +(1 row) + +drop function sp_add_user(text); +drop function sp_id_user(text); +-- +-- tests for refcursors +-- +create table rc_test (a int, b int); +copy rc_test from stdin; +create function return_refcursor(rc refcursor) returns refcursor as $$ +begin + open rc for select a from rc_test; + return rc; +end +$$ language plpgsql; +create function refcursor_test1(refcursor) returns refcursor as $$ +begin + perform return_refcursor($1); + return $1; +end +$$ language plpgsql; +begin; +select refcursor_test1('test1'); + refcursor_test1 +----------------- + test1 +(1 row) + +fetch next in test1; + a +--- + 5 +(1 row) + +select refcursor_test1('test2'); + refcursor_test1 +----------------- + test2 +(1 row) + +fetch all from test2; + a +----- + 5 + 50 + 500 +(3 rows) + +commit; +-- should fail +fetch next from test1; +ERROR: cursor "test1" does not exist +create function refcursor_test2(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; + nonsense record; +begin + open c1($1, $2); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +-- PGXCTODO: This is failing due to issue 3522907, complicated SELECT queries in plpgsql functions +select refcursor_test2(20000, 20000) as "Should be false", + refcursor_test2(20, 20) as "Should be true"; + Should be false | Should be true +-----------------+---------------- + f | t +(1 row) + +-- +-- tests for cursors with named parameter arguments +-- +create function namedparmcursor_test1(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param12 int) for select * from rc_test where a > param1 and b > param12; + nonsense record; +begin + open c1(param12 := $2, param1 := $1); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +select namedparmcursor_test1(20000, 20000) as "Should be false", + namedparmcursor_test1(20, 20) as "Should be true"; + Should be false | Should be true +-----------------+---------------- + f | t +(1 row) + +-- mixing named and positional argument notations +create function namedparmcursor_test2(int, int) returns boolean as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; + nonsense record; +begin + open c1(param1 := $1, $2); + fetch c1 into nonsense; + close c1; + if found then + return true; + else + return false; + end if; +end +$$ language plpgsql; +select namedparmcursor_test2(20, 20); + namedparmcursor_test2 +----------------------- + t +(1 row) + +-- mixing named and positional: param2 is given twice, once in named notation +-- and second time in positional notation. Should throw an error at parse time +create function namedparmcursor_test3() returns void as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; +begin + open c1(param2 := 20, 21); +end +$$ language plpgsql; +ERROR: value for parameter "param2" of cursor "c1" specified more than once +LINE 5: open c1(param2 := 20, 21); + ^ +-- mixing named and positional: same as previous test, but param1 is duplicated +create function namedparmcursor_test4() returns void as $$ +declare + c1 cursor (param1 int, param2 int) for select * from rc_test where a > param1 and b > param2; +begin + open c1(20, param1 := 21); +end +$$ language plpgsql; +ERROR: value for parameter "param1" of cursor "c1" specified more than once +LINE 5: open c1(20, param1 := 21); + ^ +-- duplicate named parameter, should throw an error at parse time +create function namedparmcursor_test5() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77, p2 := 42); +end +$$ language plpgsql; +ERROR: value for parameter "p2" of cursor "c1" specified more than once +LINE 6: open c1 (p2 := 77, p2 := 42); + ^ +-- not enough parameters, should throw an error at parse time +create function namedparmcursor_test6() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77); +end +$$ language plpgsql; +ERROR: not enough arguments for cursor "c1" +LINE 6: open c1 (p2 := 77); + ^ +-- division by zero runtime error, the context given in the error message +-- should be sensible +create function namedparmcursor_test7() returns void as $$ +declare + c1 cursor (p1 int, p2 int) for + select * from tenk1 where thousand = p1 and tenthous = p2; +begin + open c1 (p2 := 77, p1 := 42/0); +end $$ language plpgsql; +select namedparmcursor_test7(); +ERROR: division by zero +CONTEXT: SQL statement "SELECT 42/0 AS p1, 77 AS p2;" +PL/pgSQL function namedparmcursor_test7() line 6 at OPEN +-- check that line comments work correctly within the argument list (there +-- is some special handling of this case in the code: the newline after the +-- comment must be preserved when the argument-evaluating query is +-- constructed, otherwise the comment effectively comments out the next +-- argument, too) +create function namedparmcursor_test8() returns int4 as $$ +declare + c1 cursor (p1 int, p2 int) for + select count(*) from tenk1 where thousand = p1 and tenthous = p2; + n int4; +begin + open c1 (77 -- test + , 42); + fetch c1 into n; + return n; +end $$ language plpgsql; +select namedparmcursor_test8(); + namedparmcursor_test8 +----------------------- + 0 +(1 row) + +-- cursor parameter name can match plpgsql variable or unreserved keyword +create function namedparmcursor_test9(p1 int) returns int4 as $$ +declare + c1 cursor (p1 int, p2 int, debug int) for + select count(*) from tenk1 where thousand = p1 and tenthous = p2 + and four = debug; + p2 int4 := 1006; + n int4; +begin + open c1 (p1 := p1, p2 := p2, debug := 2); + fetch c1 into n; + return n; +end $$ language plpgsql; +select namedparmcursor_test9(6); + namedparmcursor_test9 +----------------------- + 1 +(1 row) + +-- +-- tests for "raise" processing +-- +create function raise_test1(int) returns int as $$ +begin + raise notice 'This message has too many parameters!', $1; + return $1; +end; +$$ language plpgsql; +ERROR: too many parameters specified for RAISE +CONTEXT: compilation of PL/pgSQL function "raise_test1" near line 3 +create function raise_test2(int) returns int as $$ +begin + raise notice 'This message has too few parameters: %, %, %', $1, $1; + return $1; +end; +$$ language plpgsql; +ERROR: too few parameters specified for RAISE +CONTEXT: compilation of PL/pgSQL function "raise_test2" near line 3 +create function raise_test3(int) returns int as $$ +begin + raise notice 'This message has no parameters (despite having %% signs in it)!'; + return $1; +end; +$$ language plpgsql; +select raise_test3(1); +NOTICE: This message has no parameters (despite having % signs in it)! + raise_test3 +------------- + 1 +(1 row) + +-- Test re-RAISE inside a nested exception block. This case is allowed +-- by Oracle's PL/SQL but was handled differently by PG before 9.1. +CREATE FUNCTION reraise_test() RETURNS void AS $$ +BEGIN + BEGIN + RAISE syntax_error; + EXCEPTION + WHEN syntax_error THEN + BEGIN + raise notice 'exception % thrown in inner block, reraising', sqlerrm; + RAISE; + EXCEPTION + WHEN OTHERS THEN + raise notice 'RIGHT - exception % caught in inner block', sqlerrm; + END; + END; +EXCEPTION + WHEN OTHERS THEN + raise notice 'WRONG - exception % caught in outer block', sqlerrm; +END; +$$ LANGUAGE plpgsql; +SELECT reraise_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function reraise_test() line 2 during statement block entry +-- +-- reject function definitions that contain malformed SQL queries at +-- compile-time, where possible +-- +create function bad_sql1() returns int as $$ +declare a int; +begin + a := 5; + Johnny Yuma; + a := 10; + return a; +end$$ language plpgsql; +ERROR: syntax error at or near "Johnny" +LINE 5: Johnny Yuma; + ^ +create function bad_sql2() returns int as $$ +declare r record; +begin + for r in select I fought the law, the law won LOOP + raise notice 'in loop'; + end loop; + return 5; +end;$$ language plpgsql; +ERROR: syntax error at or near "the" +LINE 4: for r in select I fought the law, the law won LOOP + ^ +-- a RETURN expression is mandatory, except for void-returning +-- functions, where it is not allowed +create function missing_return_expr() returns int as $$ +begin + return ; +end;$$ language plpgsql; +ERROR: missing expression at or near ";" +LINE 3: return ; + ^ +create function void_return_expr() returns void as $$ +begin + return 5; +end;$$ language plpgsql; +ERROR: RETURN cannot have a parameter in function returning void +LINE 3: return 5; + ^ +-- VOID functions are allowed to omit RETURN +create function void_return_expr() returns void as $$ +begin + perform 2+2; +end;$$ language plpgsql; +select void_return_expr(); + void_return_expr +------------------ + +(1 row) + +-- but ordinary functions are not +create function missing_return_expr() returns int as $$ +begin + perform 2+2; +end;$$ language plpgsql; +select missing_return_expr(); +ERROR: control reached end of function without RETURN +CONTEXT: PL/pgSQL function missing_return_expr() +drop function void_return_expr(); +drop function missing_return_expr(); +-- +-- EXECUTE ... INTO test +-- +create table eifoo (i integer, y integer); +create type eitype as (i integer, y integer); +create or replace function execute_into_test(varchar) returns record as $$ +declare + _r record; + _rt eifoo%rowtype; + _v eitype; + i int; + j int; + k int; +begin + execute 'insert into '||$1||' values(10,15)'; + execute 'select (row).* from (select row(10,1)::eifoo) s' into _r; + raise notice '% %', _r.i, _r.y; + execute 'select * from '||$1||' limit 1' into _rt; + raise notice '% %', _rt.i, _rt.y; + execute 'select *, 20 from '||$1||' limit 1' into i, j, k; + raise notice '% % %', i, j, k; + execute 'select 1,2' into _v; + return _v; +end; $$ language plpgsql; +select execute_into_test('eifoo'); +NOTICE: 10 1 +NOTICE: +NOTICE: + execute_into_test +------------------- + (1,2) +(1 row) + +drop table eifoo cascade; +drop type eitype cascade; +-- +-- SQLSTATE and SQLERRM test +-- +create function excpt_test1() returns void as $$ +begin + raise notice '% %', sqlstate, sqlerrm; +end; $$ language plpgsql; +-- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION +-- blocks +select excpt_test1(); +ERROR: column "sqlstate" does not exist +LINE 1: SELECT sqlstate + ^ +QUERY: SELECT sqlstate +CONTEXT: PL/pgSQL function excpt_test1() line 3 at RAISE +create function excpt_test2() returns void as $$ +begin + begin + begin + raise notice '% %', sqlstate, sqlerrm; + end; + end; +end; $$ language plpgsql; +-- should fail +select excpt_test2(); +ERROR: column "sqlstate" does not exist +LINE 1: SELECT sqlstate + ^ +QUERY: SELECT sqlstate +CONTEXT: PL/pgSQL function excpt_test2() line 5 at RAISE +create function excpt_test3() returns void as $$ +begin + begin + raise exception 'user exception'; + exception when others then + raise notice 'caught exception % %', sqlstate, sqlerrm; + begin + raise notice '% %', sqlstate, sqlerrm; + perform 10/0; + exception + when substring_error then + -- this exception handler shouldn't be invoked + raise notice 'unexpected exception: % %', sqlstate, sqlerrm; + when division_by_zero then + raise notice 'caught exception % %', sqlstate, sqlerrm; + end; + raise notice '% %', sqlstate, sqlerrm; + end; +end; $$ language plpgsql; +select excpt_test3(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function excpt_test3() line 3 during statement block entry +create function excpt_test4() returns text as $$ +begin + begin perform 1/0; + exception when others then return sqlerrm; end; +end; $$ language plpgsql; +select excpt_test4(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function excpt_test4() line 3 during statement block entry +drop function excpt_test1(); +drop function excpt_test2(); +drop function excpt_test3(); +drop function excpt_test4(); +-- parameters of raise stmt can be expressions +create function raise_exprs() returns void as $$ +declare + a integer[] = '{10,20,30}'; + c varchar = 'xyz'; + i integer; +begin + i := 2; + raise notice '%; %; %; %; %; %', a, a[i], c, (select c || 'abc'), row(10,'aaa',NULL,30), NULL; +end;$$ language plpgsql; +select raise_exprs(); +NOTICE: {10,20,30}; 20; xyz; xyzabc; (10,aaa,,30); + raise_exprs +------------- + +(1 row) + +drop function raise_exprs(); +-- continue statement +create table conttesttbl(idx serial, v integer); +insert into conttesttbl(v) values(10); +insert into conttesttbl(v) values(20); +insert into conttesttbl(v) values(30); +insert into conttesttbl(v) values(40); +create function continue_test1() returns void as $$ +declare _i integer = 0; _r record; +begin + raise notice '---1---'; + loop + _i := _i + 1; + raise notice '%', _i; + continue when _i < 10; + exit; + end loop; + + raise notice '---2---'; + <> + loop + _i := _i - 1; + loop + raise notice '%', _i; + continue lbl when _i > 0; + exit lbl; + end loop; + end loop; + + raise notice '---3---'; + <> + while _i < 10 loop + _i := _i + 1; + continue the_loop when _i % 2 = 0; + raise notice '%', _i; + end loop; + + raise notice '---4---'; + for _i in 1..10 loop + begin + -- applies to outer loop, not the nested begin block + continue when _i < 5; + raise notice '%', _i; + end; + end loop; + + raise notice '---5---'; + for _r in select * from conttesttbl loop + continue when _r.v <= 20; + raise notice '%', _r.v; + end loop; + + raise notice '---6---'; + for _r in execute 'select * from conttesttbl' loop + continue when _r.v <= 20; + raise notice '%', _r.v; + end loop; + + raise notice '---7---'; + for _i in 1..3 loop + raise notice '%', _i; + continue when _i = 3; + end loop; + + raise notice '---8---'; + _i := 1; + while _i <= 3 loop + raise notice '%', _i; + _i := _i + 1; + continue when _i = 3; + end loop; + + raise notice '---9---'; + for _r in select * from conttesttbl order by v limit 1 loop + raise notice '%', _r.v; + continue; + end loop; + + raise notice '---10---'; + for _r in execute 'select * from conttesttbl order by v limit 1' loop + raise notice '%', _r.v; + continue; + end loop; +end; $$ language plpgsql; +select continue_test1(); +NOTICE: ---1--- +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 +NOTICE: 6 +NOTICE: 7 +NOTICE: 8 +NOTICE: 9 +NOTICE: 10 +NOTICE: ---2--- +NOTICE: 9 +NOTICE: 8 +NOTICE: 7 +NOTICE: 6 +NOTICE: 5 +NOTICE: 4 +NOTICE: 3 +NOTICE: 2 +NOTICE: 1 +NOTICE: 0 +NOTICE: ---3--- +NOTICE: 1 +NOTICE: 3 +NOTICE: 5 +NOTICE: 7 +NOTICE: 9 +NOTICE: ---4--- +NOTICE: 5 +NOTICE: 6 +NOTICE: 7 +NOTICE: 8 +NOTICE: 9 +NOTICE: 10 +NOTICE: ---5--- +NOTICE: 30 +NOTICE: 40 +NOTICE: ---6--- +NOTICE: 30 +NOTICE: 40 +NOTICE: ---7--- +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: ---8--- +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: ---9--- +NOTICE: 10 +NOTICE: ---10--- +NOTICE: 10 + continue_test1 +---------------- + +(1 row) + +-- CONTINUE is only legal inside a loop +create function continue_test2() returns void as $$ +begin + begin + continue; + end; + return; +end; +$$ language plpgsql; +-- should fail +select continue_test2(); +ERROR: CONTINUE cannot be used outside a loop +CONTEXT: PL/pgSQL function continue_test2() +-- CONTINUE can't reference the label of a named block +create function continue_test3() returns void as $$ +begin + <> + begin + loop + continue begin_block1; + end loop; + end; +end; +$$ language plpgsql; +-- should fail +select continue_test3(); +ERROR: CONTINUE cannot be used outside a loop +CONTEXT: PL/pgSQL function continue_test3() +drop function continue_test1(); +drop function continue_test2(); +drop function continue_test3(); +drop table conttesttbl; +-- verbose end block and end loop +create function end_label1() returns void as $$ +<> +begin + <> + for _i in 1 .. 10 loop + exit flbl1; + end loop flbl1; + <> + for _i in 1 .. 10 loop + exit flbl2; + end loop; +end blbl; +$$ language plpgsql; +select end_label1(); + end_label1 +------------ + +(1 row) + +drop function end_label1(); +-- should fail: undefined end label +create function end_label2() returns void as $$ +begin + for _i in 1 .. 10 loop + exit; + end loop flbl1; +end; +$$ language plpgsql; +ERROR: label does not exist at or near "flbl1" +LINE 5: end loop flbl1; + ^ +-- should fail: end label does not match start label +create function end_label3() returns void as $$ +<> +begin + <> + for _i in 1 .. 10 loop + exit; + end loop outer_label; +end; +$$ language plpgsql; +ERROR: end label "outer_label" differs from block's label "inner_label" +LINE 7: end loop outer_label; + ^ +-- should fail: end label on a block without a start label +create function end_label4() returns void as $$ +<> +begin + for _i in 1 .. 10 loop + exit; + end loop outer_label; +end; +$$ language plpgsql; +ERROR: end label "outer_label" specified for unlabelled block +LINE 6: end loop outer_label; + ^ +-- using list of scalars in fori and fore stmts +create function for_vect() returns void as $proc$ +<>declare a integer; b varchar; c varchar; r record; +begin + -- fori + for i in 1 .. 3 loop + raise notice '%', i; + end loop; + -- fore with record var + for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop + raise notice '% % %', r.aa, r.bb, r.cc; + end loop; + -- fore with single scalar + for a in select gs from generate_series(1,4) gs loop + raise notice '%', a; + end loop; + -- fore with multiple scalars + for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop + raise notice '% % %', a, b, c; + end loop; + -- using qualified names in fors, fore is enabled, disabled only for fori + for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop + raise notice '% % %', a, b, c; + end loop; +end; +$proc$ language plpgsql; +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 +-- a complaint about an ungrouped column reference. +create function multi_datum_use(p1 int) returns bool as $$ +declare + x int; + y int; +begin + select into x,y unique1/p1, unique1/$1 from tenk1 group by unique1/p1; + return x = y; +end$$ language plpgsql; +select multi_datum_use(42); + multi_datum_use +----------------- + t +(1 row) + +-- +-- Test STRICT limiter in both planned and EXECUTE invocations. +-- Note that a data-modifying query is quasi strict (disallow multi rows) +-- by default in the planned case, but not in EXECUTE. +-- +create temp table foo (f1 int, f2 int); +insert into foo values (1,2), (3,4); +create or replace function footest() returns void as $$ +declare x record; +begin + -- should work + insert into foo values(5,6) returning * into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +NOTICE: x.f1 = 5, x.f2 = 6 + footest +--------- + +(1 row) + +create or replace function footest() returns void as $$ +declare x record; +begin + -- should fail due to implicit strict + insert into foo values(7,8),(9,10) returning * into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: consistency check on SPI tuple count failed +CONTEXT: SQL statement "insert into foo values(7,8),(9,10) returning *" +PL/pgSQL function footest() line 5 at SQL statement +create or replace function footest() returns void as $$ +declare x record; +begin + -- should work + execute 'insert into foo values(5,6) returning *' into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +NOTICE: x.f1 = 5, x.f2 = 6 + footest +--------- + +(1 row) + +create or replace function footest() returns void as $$ +declare x record; +begin + -- this should work since EXECUTE isn't as picky + execute 'insert into foo values(7,8),(9,10) returning *' into x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +NOTICE: x.f1 = 9, x.f2 = 10 + footest +--------- + +(1 row) + +select * from foo order by 1, 2; + f1 | f2 +----+---- + 1 | 2 + 3 | 4 + 5 | 6 + 5 | 6 + 7 | 8 + 9 | 10 +(6 rows) + +create or replace function footest() returns void as $$ +declare x record; +begin + -- should work + select * from foo where f1 = 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +NOTICE: x.f1 = 3, x.f2 = 4 + footest +--------- + +(1 row) + +create or replace function footest() returns void as $$ +declare x record; +begin + -- should fail, no rows + select * from foo where f1 = 0 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned no rows +CONTEXT: PL/pgSQL function footest() line 5 at SQL statement +create or replace function footest() returns void as $$ +declare x record; +begin + -- should fail, too many rows + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function footest() line 5 at SQL statement +create or replace function footest() returns void as $$ +declare x record; +begin + -- should work + execute 'select * from foo where f1 = 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +NOTICE: x.f1 = 3, x.f2 = 4 + footest +--------- + +(1 row) + +create or replace function footest() returns void as $$ +declare x record; +begin + -- should fail, no rows + execute 'select * from foo where f1 = 0' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned no rows +CONTEXT: PL/pgSQL function footest() line 5 at EXECUTE +create or replace function footest() returns void as $$ +declare x record; +begin + -- should fail, too many rows + execute 'select * from foo where f1 > 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function footest() line 5 at EXECUTE +drop function footest(); +-- test printing parameters after failure due to STRICT +set plpgsql.print_strict_params to true; +create or replace function footest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- no rows + select * from foo where f1 = p1 and f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned no rows +DETAIL: parameters: p1 = '2', p3 = 'foo' +CONTEXT: PL/pgSQL function footest() line 8 at SQL statement +create or replace function footest() returns void as $$ +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +DETAIL: parameters: p1 = '2', p3 = 'foo' +CONTEXT: PL/pgSQL function footest() line 8 at SQL statement +create or replace function footest() returns void as $$ +declare x record; +begin + -- too many rows, no params + select * from foo where f1 > 3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function footest() line 5 at SQL statement +create or replace function footest() returns void as $$ +declare x record; +begin + -- no rows + execute 'select * from foo where f1 = $1 or f1::text = $2' using 0, 'foo' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned no rows +DETAIL: parameters: $1 = '0', $2 = 'foo' +CONTEXT: PL/pgSQL function footest() line 5 at EXECUTE +create or replace function footest() returns void as $$ +declare x record; +begin + -- too many rows + execute 'select * from foo where f1 > $1' using 1 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +DETAIL: parameters: $1 = '1' +CONTEXT: PL/pgSQL function footest() line 5 at EXECUTE +create or replace function footest() returns void as $$ +declare x record; +begin + -- too many rows, no parameters + execute 'select * from foo where f1 > 3' into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function footest() line 5 at EXECUTE +create or replace function footest() returns void as $$ +-- override the global +#print_strict_params off +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +CONTEXT: PL/pgSQL function footest() line 10 at SQL statement +reset plpgsql.print_strict_params; +create or replace function footest() returns void as $$ +-- override the global +#print_strict_params on +declare +x record; +p1 int := 2; +p3 text := 'foo'; +begin + -- too many rows + select * from foo where f1 > p1 or f1::text = p3 into strict x; + raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2; +end$$ language plpgsql; +select footest(); +ERROR: query returned more than one row +DETAIL: parameters: p1 = '2', p3 = 'foo' +CONTEXT: PL/pgSQL function footest() line 10 at SQL statement +-- test warnings and errors +set plpgsql.extra_warnings to 'all'; +ERROR: syntax error at or near "all" +set plpgsql.extra_warnings to 'none'; +set plpgsql.extra_errors to 'all'; +ERROR: syntax error at or near "all" +set plpgsql.extra_errors to 'none'; +-- test warnings when shadowing a variable +set plpgsql.extra_warnings to 'shadowed_variables'; +-- simple shadowing of input and output parameters +create or replace function shadowtest(in1 int) + returns table (out1 int) as $$ +declare +in1 int; +out1 int; +begin +end +$$ language plpgsql; +WARNING: variable "in1" shadows a previously defined variable +LINE 4: in1 int; + ^ +WARNING: variable "out1" shadows a previously defined variable +LINE 5: out1 int; + ^ +select shadowtest(1); + shadowtest +------------ +(0 rows) + +set plpgsql.extra_warnings to 'shadowed_variables'; +select shadowtest(1); + shadowtest +------------ +(0 rows) + +create or replace function shadowtest(in1 int) + returns table (out1 int) as $$ +declare +in1 int; +out1 int; +begin +end +$$ language plpgsql; +WARNING: variable "in1" shadows a previously defined variable +LINE 4: in1 int; + ^ +WARNING: variable "out1" shadows a previously defined variable +LINE 5: out1 int; + ^ +select shadowtest(1); + shadowtest +------------ +(0 rows) + +drop function shadowtest(int); +-- shadowing in a second DECLARE block +create or replace function shadowtest() + returns void as $$ +declare +f1 int; +begin + declare + f1 int; + begin + end; +end$$ language plpgsql; +WARNING: variable "f1" shadows a previously defined variable +LINE 7: f1 int; + ^ +drop function shadowtest(); +-- several levels of shadowing +create or replace function shadowtest(in1 int) + returns void as $$ +declare +in1 int; +begin + declare + in1 int; + begin + end; +end$$ language plpgsql; +WARNING: variable "in1" shadows a previously defined variable +LINE 4: in1 int; + ^ +WARNING: variable "in1" shadows a previously defined variable +LINE 7: in1 int; + ^ +drop function shadowtest(int); +-- shadowing in cursor definitions +create or replace function shadowtest() + returns void as $$ +declare +f1 int; +c1 cursor (f1 int) for select 1; +begin +end$$ language plpgsql; +WARNING: variable "f1" shadows a previously defined variable +LINE 5: c1 cursor (f1 int) for select 1; + ^ +drop function shadowtest(); +-- test errors when shadowing a variable +set plpgsql.extra_errors to 'shadowed_variables'; +create or replace function shadowtest(f1 int) + returns boolean as $$ +declare f1 int; begin return 1; end $$ language plpgsql; +ERROR: variable "f1" shadows a previously defined variable +LINE 3: declare f1 int; begin return 1; end $$ language plpgsql; + ^ +select shadowtest(1); +ERROR: function shadowtest(integer) does not exist +LINE 1: select shadowtest(1); + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +reset plpgsql.extra_errors; +reset plpgsql.extra_warnings; +create or replace function shadowtest(f1 int) + returns boolean as $$ +declare f1 int; begin return 1; end $$ language plpgsql; +select shadowtest(1); + shadowtest +------------ + t +(1 row) + +-- test scrollable cursor support +create function sc_test() returns setof integer as $$ +declare + c scroll cursor for select f1 from int4_tbl; + x integer; +begin + open c; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test() order by 1; + sc_test +--------- +(0 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c no scroll cursor for select f1 from int4_tbl; + x integer; +begin + open c; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test() order by 1; -- fails because of NO SCROLL specification +ERROR: cursor can only scan forward +HINT: Declare it with SCROLL option to enable backward scan. +CONTEXT: PL/pgSQL function sc_test() line 7 at FETCH +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for select f1 from int4_tbl; + fetch last from c into x; + while found loop + return next x; + fetch prior from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test() order by 1; + sc_test +--------- +(0 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for execute 'select f1 from int4_tbl order by 1'; + fetch last from c into x; + while found loop + return next x; + fetch relative -2 from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test(); + sc_test +--------- +(0 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c refcursor; + x integer; +begin + open c scroll for execute 'select f1 from int4_tbl order by 1'; + fetch last from c into x; + while found loop + return next x; + move backward 2 from c; + fetch relative -1 from c into x; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test(); + sc_test +--------- +(0 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c cursor for select * from generate_series(1, 10); + x integer; +begin + open c; + loop + move relative 2 in c; + if not found then + exit; + end if; + fetch next from c into x; + if found then + return next x; + end if; + end loop; + close c; +end; +$$ language plpgsql; +select * from sc_test() order by 1; + sc_test +--------- + 3 + 6 + 9 +(3 rows) + +create or replace function sc_test() returns setof integer as $$ +declare + c cursor for select * from generate_series(1, 10); + x integer; +begin + open c; + move forward all in c; + fetch backward from c into x; + if found then + return next x; + end if; + close c; +end; +$$ language plpgsql; +select * from sc_test() order by 1; + sc_test +--------- + 10 +(1 row) + +drop function sc_test(); +-- test qualified variable names +create function pl_qual_names (param1 int) returns void as $$ +<> +declare + param1 int := 1; +begin + <> + declare + param1 int := 2; + begin + raise notice 'param1 = %', param1; + raise notice 'pl_qual_names.param1 = %', pl_qual_names.param1; + raise notice 'outerblock.param1 = %', outerblock.param1; + raise notice 'innerblock.param1 = %', innerblock.param1; + end; +end; +$$ language plpgsql; +select pl_qual_names(42); +NOTICE: param1 = 2 +NOTICE: pl_qual_names.param1 = 42 +NOTICE: outerblock.param1 = 1 +NOTICE: innerblock.param1 = 2 + pl_qual_names +--------------- + +(1 row) + +drop function pl_qual_names(int); +-- tests for RETURN QUERY +create function ret_query1(out int, out int) returns setof record as $$ +begin + $1 := -1; + $2 := -2; + return next; + return query select x + 1, x * 10 from generate_series(0, 10) s (x); + return next; +end; +$$ language plpgsql; +select * from ret_query1() order by 1, 2; + column1 | column2 +---------+--------- + -1 | -2 + -1 | -2 + 1 | 0 + 2 | 10 + 3 | 20 + 4 | 30 + 5 | 40 + 6 | 50 + 7 | 60 + 8 | 70 + 9 | 80 + 10 | 90 + 11 | 100 +(13 rows) + +create type record_type as (x text, y int, z boolean); +create or replace function ret_query2(lim int) returns setof record_type as $$ +begin + return query select md5(s.x::text), s.x, s.x > 0 + from generate_series(-8, lim) s (x) where s.x % 2 = 0; +end; +$$ language plpgsql; +select * from ret_query2(8) order by 1; + x | y | z +----------------------------------+----+--- + 0267aaf632e87a63288a08331f22c7c3 | -4 | f + 1679091c5a880faf6fb5e6087eb1b2dc | 6 | t + 596a3d04481816330f07e4f97510c28f | -6 | f + 5d7b9adcbe1c629ec722529dd12e5129 | -2 | f + a87ff679a2f3e71d9181a67b7542122c | 4 | t + a8d2ec85eaf98407310b72eb73dda247 | -8 | f + c81e728d9d4c2f636f067f89cc14862c | 2 | t + c9f0f895fb98ab9159f51fd0297e236d | 8 | t + cfcd208495d565ef66e7dff9f98764da | 0 | f +(9 rows) + +-- test EXECUTE USING +create function exc_using(int, text) returns int as $$ +declare i int; +begin + for i in execute 'select * from generate_series(1,$1)' using $1+1 loop + raise notice '%', i; + end loop; + execute 'select $2 + $2*3 + length($1)' into i using $2,$1; + return i; +end +$$ language plpgsql; +select exc_using(5, 'foobar'); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 +NOTICE: 6 + exc_using +----------- + 26 +(1 row) + +drop function exc_using(int, text); +create or replace function exc_using(int) returns void as $$ +declare + c refcursor; + i int; +begin + open c for execute 'select * from generate_series(1,$1)' using $1+1; + loop + fetch c into i; + exit when not found; + raise notice '%', i; + end loop; + close c; + return; +end; +$$ language plpgsql; +select exc_using(5); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 +NOTICE: 6 + exc_using +----------- + +(1 row) + +drop function exc_using(int); +-- test FOR-over-cursor +create or replace function forc01() returns void as $$ +declare + c cursor(r1 integer, r2 integer) + for select * from generate_series(r1,r2) i; + c2 cursor + for select * from generate_series(41,43) i; +begin + for r in c(5,7) loop + raise notice '% from %', r.i, c; + end loop; + -- again, to test if cursor was closed properly + for r in c(9,10) loop + raise notice '% from %', r.i, c; + end loop; + -- and test a parameterless cursor + for r in c2 loop + raise notice '% from %', r.i, c2; + end loop; + -- and try it with a hand-assigned name + raise notice 'after loop, c2 = %', c2; + c2 := 'special_name'; + for r in c2 loop + raise notice '% from %', r.i, c2; + end loop; + raise notice 'after loop, c2 = %', c2; + -- and try it with a generated name + -- (which we can't show in the output because it's variable) + c2 := null; + for r in c2 loop + raise notice '%', r.i; + end loop; + raise notice 'after loop, c2 = %', c2; + return; +end; +$$ language plpgsql; +select forc01(); +NOTICE: 5 from c +NOTICE: 6 from c +NOTICE: 7 from c +NOTICE: 9 from c +NOTICE: 10 from c +NOTICE: 41 from c2 +NOTICE: 42 from c2 +NOTICE: 43 from c2 +NOTICE: after loop, c2 = c2 +NOTICE: 41 from special_name +NOTICE: 42 from special_name +NOTICE: 43 from special_name +NOTICE: after loop, c2 = special_name +NOTICE: 41 +NOTICE: 42 +NOTICE: 43 +NOTICE: after loop, c2 = + forc01 +-------- + +(1 row) + +-- try updating the cursor's current row +create temp table forc_test as + select n as i, n as j from generate_series(1,10) n; +create or replace function forc01() returns void as $$ +declare + c cursor for select * from forc_test; +begin + for r in c loop + raise notice '%, %', r.i, r.j; + update forc_test set i = i * 100, j = r.j * 2 where current of c; + end loop; +end; +$$ language plpgsql; +select forc01(); +NOTICE: 1, 1 +ERROR: WHERE CURRENT OF clause not yet supported +CONTEXT: SQL statement "update forc_test set i = i * 100, j = r.j * 2 where current of c" +PL/pgSQL function forc01() line 7 at SQL statement +select * from forc_test order by 1, 2; + i | j +----+---- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 + 10 | 10 +(10 rows) + +-- same, with a cursor whose portal name doesn't match variable name +create or replace function forc01() returns void as $$ +declare + c refcursor := 'fooled_ya'; + r record; +begin + open c for select * from forc_test; + loop + fetch c into r; + exit when not found; + raise notice '%, %', r.i, r.j; + update forc_test set i = i * 100, j = r.j * 2 where current of c; + end loop; +end; +$$ language plpgsql; +select forc01(); +NOTICE: 1, 1 +ERROR: WHERE CURRENT OF clause not yet supported +CONTEXT: SQL statement "update forc_test set i = i * 100, j = r.j * 2 where current of c" +PL/pgSQL function forc01() line 11 at SQL statement +select * from forc_test order by 1; + i | j +----+---- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 + 10 | 10 +(10 rows) + +drop function forc01(); +-- fail because cursor has no query bound to it +create or replace function forc_bad() returns void as $$ +declare + c refcursor; +begin + for r in c loop + raise notice '%', r.i; + end loop; +end; +$$ language plpgsql; +ERROR: cursor FOR loop must use a bound cursor variable +LINE 5: for r in c loop + ^ +-- test RETURN QUERY EXECUTE +create or replace function return_dquery() +returns setof int as $$ +begin + return query execute 'select * from (values(10),(20)) f'; + return query execute 'select * from (values($1),($2)) f' using 40,50; +end; +$$ language plpgsql; +select * from return_dquery() order by 1; + return_dquery +--------------- + 10 + 20 + 40 + 50 +(4 rows) + +drop function return_dquery(); +-- test RETURN QUERY with dropped columns +create table tabwithcols(a int, b int, c int, d int); +insert into tabwithcols values(10,20,30,40),(50,60,70,80); +create or replace function returnqueryf() +returns setof tabwithcols as $$ +begin + return query select * from tabwithcols; + return query execute 'select * from tabwithcols'; +end; +$$ language plpgsql; +select * from returnqueryf() order by 1,2,3,4; + a | b | c | d +----+----+----+---- + 10 | 20 | 30 | 40 + 10 | 20 | 30 | 40 + 50 | 60 | 70 | 80 + 50 | 60 | 70 | 80 +(4 rows) + +alter table tabwithcols drop column b; +select * from returnqueryf() order by 1,2,3; + a | c | d +----+----+---- + 10 | 30 | 40 + 10 | 30 | 40 + 50 | 70 | 80 + 50 | 70 | 80 +(4 rows) + +alter table tabwithcols drop column d; +select * from returnqueryf() order by 1,2; + a | c +----+---- + 10 | 30 + 10 | 30 + 50 | 70 + 50 | 70 +(4 rows) + +alter table tabwithcols add column d int; +select * from returnqueryf() order by 1,2,3; + a | c | d +----+----+--- + 10 | 30 | + 10 | 30 | + 50 | 70 | + 50 | 70 | +(4 rows) + +drop function returnqueryf(); +drop table tabwithcols; +-- +-- Tests for composite-type results +-- +create type compostype as (x int, y varchar); +-- test: use of variable of composite type in return statement +create or replace function compos() returns compostype as $$ +declare + v compostype; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +-- test: use of variable of record type in return statement +create or replace function compos() returns compostype as $$ +declare + v record; +begin + v := (1, 'hello'::varchar); + return v; +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +-- test: use of row expr in return statement +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello'::varchar); +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +-- this does not work currently (no implicit casting) +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello'); +end; +$$ language plpgsql; +select compos(); +ERROR: returned record type does not match expected record type +DETAIL: Returned type unknown does not match expected type character varying in column 2. +CONTEXT: PL/pgSQL function compos() while casting return value to function's return type +-- ... but this does +create or replace function compos() returns compostype as $$ +begin + return (1, 'hello')::compostype; +end; +$$ language plpgsql; +select compos(); + compos +----------- + (1,hello) +(1 row) + +drop function compos(); +-- test: return a row expr as record. +create or replace function composrec() returns record as $$ +declare + v record; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; +select composrec(); + composrec +----------- + (1,hello) +(1 row) + +-- test: return row expr in return statement. +create or replace function composrec() returns record as $$ +begin + return (1, 'hello'); +end; +$$ language plpgsql; +select composrec(); + composrec +----------- + (1,hello) +(1 row) + +drop function composrec(); +-- test: row expr in RETURN NEXT statement. +create or replace function compos() returns setof compostype as $$ +begin + for i in 1..3 + loop + return next (1, 'hello'::varchar); + end loop; + return next null::compostype; + return next (2, 'goodbye')::compostype; +end; +$$ language plpgsql; +select * from compos(); + x | y +---+--------- + 1 | hello + 1 | hello + 1 | hello + | + 2 | goodbye +(5 rows) + +drop function compos(); +-- test: use invalid expr in return statement. +create or replace function compos() returns compostype as $$ +begin + return 1 + 1; +end; +$$ language plpgsql; +select compos(); +ERROR: cannot return non-composite value from function returning composite type +CONTEXT: PL/pgSQL function compos() line 3 at RETURN +-- RETURN variable is a different code path ... +create or replace function compos() returns compostype as $$ +declare x int := 42; +begin + return x; +end; +$$ language plpgsql; +select * from compos(); +ERROR: cannot return non-composite value from function returning composite type +CONTEXT: PL/pgSQL function compos() line 4 at RETURN +drop function compos(); +-- test: invalid use of composite variable in scalar-returning function +create or replace function compos() returns int as $$ +declare + v compostype; +begin + v := (1, 'hello'); + return v; +end; +$$ language plpgsql; +select compos(); +ERROR: invalid input syntax for integer: "(1,hello)" +CONTEXT: PL/pgSQL function compos() while casting return value to function's return type +-- test: invalid use of composite expression in scalar-returning function +create or replace function compos() returns int as $$ +begin + return (1, 'hello')::compostype; +end; +$$ language plpgsql; +select compos(); +ERROR: invalid input syntax for integer: "(1,hello)" +CONTEXT: PL/pgSQL function compos() while casting return value to function's return type +drop function compos(); +drop type compostype; +-- +-- Tests for 8.4's new RAISE features +-- +create or replace function raise_test() returns void as $$ +begin + raise notice '% % %', 1, 2, 3 + using errcode = '55001', detail = 'some detail info', hint = 'some hint'; + raise '% % %', 1, 2, 3 + using errcode = 'division_by_zero', detail = 'some detail info'; +end; +$$ language plpgsql; +select raise_test(); +NOTICE: 1 2 3 +DETAIL: some detail info +HINT: some hint +ERROR: 1 2 3 +DETAIL: some detail info +-- Since we can't actually see the thrown SQLSTATE in default psql output, +-- test it like this; this also tests re-RAISE +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = 'division_by_zero', detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function raise_test() line 2 during statement block entry +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = '1234F', detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function raise_test() line 2 during statement block entry +-- SQLSTATE specification in WHEN +create or replace function raise_test() returns void as $$ +begin + raise 'check me' + using errcode = '1234F', detail = 'some detail info'; + exception + when sqlstate '1234F' then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function raise_test() line 2 during statement block entry +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using detail = 'some detail info'; + exception + when others then + raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm; + raise; +end; +$$ language plpgsql; +select raise_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function raise_test() line 2 during statement block entry +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero; +end; +$$ language plpgsql; +select raise_test(); +ERROR: division_by_zero +create or replace function raise_test() returns void as $$ +begin + raise sqlstate '1234F'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: 1234F +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using message = 'custom' || ' message'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: custom message +create or replace function raise_test() returns void as $$ +begin + raise using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: custom message +-- conflict on message +create or replace function raise_test() returns void as $$ +begin + raise notice 'some message' using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: RAISE option already specified: MESSAGE +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- conflict on errcode +create or replace function raise_test() returns void as $$ +begin + raise division_by_zero using message = 'custom' || ' message', errcode = '22012'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: RAISE option already specified: ERRCODE +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- nothing to re-RAISE +create or replace function raise_test() returns void as $$ +begin + raise; +end; +$$ language plpgsql; +select raise_test(); +ERROR: RAISE without parameters cannot be used outside an exception handler +CONTEXT: PL/pgSQL function raise_test() line 3 at RAISE +-- test access to exception data +create function zero_divide() returns int as $$ +declare v int := 0; +begin + return 10 / v; +end; +$$ language plpgsql; +create or replace function raise_test() returns void as $$ +begin + raise exception 'custom exception' + using detail = 'some detail of custom exception', + hint = 'some hint related to custom exception'; +end; +$$ language plpgsql; +create function stacked_diagnostics_test() returns void as $$ +declare _sqlstate text; + _message text; + _context text; +begin + perform zero_divide(); +exception when others then + get stacked diagnostics + _sqlstate = returned_sqlstate, + _message = message_text, + _context = pg_exception_context; + raise notice 'sqlstate: %, message: %, context: [%]', + _sqlstate, _message, replace(_context, E'\n', ' <- '); +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function stacked_diagnostics_test() line 5 during statement block entry +create or replace function stacked_diagnostics_test() returns void as $$ +declare _detail text; + _hint text; + _message text; +begin + perform raise_test(); +exception when others then + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function stacked_diagnostics_test() line 5 during statement block entry +-- fail, cannot use stacked diagnostics statement outside handler +create or replace function stacked_diagnostics_test() returns void as $$ +declare _detail text; + _hint text; + _message text; +begin + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +ERROR: GET STACKED DIAGNOSTICS cannot be used outside an exception handler +CONTEXT: PL/pgSQL function stacked_diagnostics_test() line 6 at GET STACKED DIAGNOSTICS +drop function zero_divide(); +drop function stacked_diagnostics_test(); +-- check cases where implicit SQLSTATE variable could be confused with +-- SQLSTATE as a keyword, cf bug #5524 +create or replace function raise_test() returns void as $$ +begin + perform 1/0; +exception + when sqlstate '22012' then + raise notice using message = sqlstate; + raise sqlstate '22012' using message = 'substitute message'; +end; +$$ language plpgsql; +select raise_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function raise_test() line 2 during statement block entry +drop function raise_test(); +-- test passing column_name, constraint_name, datatype_name, table_name +-- and schema_name error fields +create or replace function stacked_diagnostics_test() returns void as $$ +declare _column_name text; + _constraint_name text; + _datatype_name text; + _table_name text; + _schema_name text; +begin + raise exception using + column = '>>some column name<<', + constraint = '>>some constraint name<<', + datatype = '>>some datatype name<<', + table = '>>some table name<<', + schema = '>>some schema name<<'; +exception when others then + get stacked diagnostics + _column_name = column_name, + _constraint_name = constraint_name, + _datatype_name = pg_datatype_name, + _table_name = table_name, + _schema_name = schema_name; + raise notice 'column %, constraint %, type %, table %, schema %', + _column_name, _constraint_name, _datatype_name, _table_name, _schema_name; +end; +$$ language plpgsql; +select stacked_diagnostics_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function stacked_diagnostics_test() line 7 during statement block entry +drop function stacked_diagnostics_test(); +-- test CASE statement +create or replace function case_test(bigint) returns text as $$ +declare a int = 10; + b int = 1; +begin + case $1 + when 1 then + return 'one'; + when 2 then + return 'two'; + when 3,4,3+5 then + return 'three, four or eight'; + when a then + return 'ten'; + when a+b, a+b+1 then + return 'eleven, twelve'; + end case; +end; +$$ language plpgsql immutable; +select case_test(1); + case_test +----------- + one +(1 row) + +select case_test(2); + case_test +----------- + two +(1 row) + +select case_test(3); + case_test +---------------------- + three, four or eight +(1 row) + +select case_test(4); + case_test +---------------------- + three, four or eight +(1 row) + +select case_test(5); -- fails +ERROR: case not found +HINT: CASE statement is missing ELSE part. +CONTEXT: PL/pgSQL function case_test(bigint) line 5 at CASE +select case_test(8); + case_test +---------------------- + three, four or eight +(1 row) + +select case_test(10); + case_test +----------- + ten +(1 row) + +select case_test(11); + case_test +---------------- + eleven, twelve +(1 row) + +select case_test(12); + case_test +---------------- + eleven, twelve +(1 row) + +select case_test(13); -- fails +ERROR: case not found +HINT: CASE statement is missing ELSE part. +CONTEXT: PL/pgSQL function case_test(bigint) line 5 at CASE +create or replace function catch() returns void as $$ +begin + raise notice '%', case_test(6); +exception + when case_not_found then + raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM; +end +$$ language plpgsql; +select catch(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function catch() line 2 during statement block entry +-- test the searched variant too, as well as ELSE +create or replace function case_test(bigint) returns text as $$ +declare a int = 10; +begin + case + when $1 = 1 then + return 'one'; + when $1 = a + 2 then + return 'twelve'; + else + return 'other'; + end case; +end; +$$ language plpgsql immutable; +select case_test(1); + case_test +----------- + one +(1 row) + +select case_test(2); + case_test +----------- + other +(1 row) + +select case_test(12); + case_test +----------- + twelve +(1 row) + +select case_test(13); + case_test +----------- + other +(1 row) + +drop function catch(); +drop function case_test(bigint); +-- test variadic functions +create or replace function vari(variadic int[]) +returns void as $$ +begin + for i in array_lower($1,1)..array_upper($1,1) loop + raise notice '%', $1[i]; + end loop; end; +$$ language plpgsql; +select vari(1,2,3,4,5); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 + vari +------ + +(1 row) + +select vari(3,4,5); +NOTICE: 3 +NOTICE: 4 +NOTICE: 5 + vari +------ + +(1 row) + +select vari(variadic array[5,6,7]); +NOTICE: 5 +NOTICE: 6 +NOTICE: 7 + vari +------ + +(1 row) + +drop function vari(int[]); +-- coercion test +create or replace function pleast(variadic numeric[]) +returns numeric as $$ +declare aux numeric = $1[array_lower($1,1)]; +begin + for i in array_lower($1,1)+1..array_upper($1,1) loop + if $1[i] < aux then aux := $1[i]; end if; + end loop; + return aux; +end; +$$ language plpgsql immutable strict; +select pleast(10,1,2,3,-16); + pleast +-------- + -16 +(1 row) + +select pleast(10.2,2.2,-1.1); + pleast +-------- + -1.1 +(1 row) + +select pleast(10.2,10, -20); + pleast +-------- + -20 +(1 row) + +select pleast(10,20, -1.0); + pleast +-------- + -1.0 +(1 row) + +-- in case of conflict, non-variadic version is preferred +create or replace function pleast(numeric) +returns numeric as $$ +begin + raise notice 'non-variadic function called'; + return $1; +end; +$$ language plpgsql immutable strict; +select pleast(10); +NOTICE: non-variadic function called + pleast +-------- + 10 +(1 row) + +drop function pleast(numeric[]); +drop function pleast(numeric); +-- test table functions +create function tftest(int) returns table(a int, b int) as $$ +begin + return query select $1, $1+i from generate_series(1,5) g(i); +end; +$$ language plpgsql immutable strict; +select * from tftest(10) order by 1, 2; + a | b +----+---- + 10 | 11 + 10 | 12 + 10 | 13 + 10 | 14 + 10 | 15 +(5 rows) + +create or replace function tftest(a1 int) returns table(a int, b int) as $$ +begin + a := a1; b := a1 + 1; + return next; + a := a1 * 10; b := a1 * 10 + 1; + return next; +end; +$$ language plpgsql immutable strict; +select * from tftest(10) order by 1, 2; + a | b +-----+----- + 10 | 11 + 100 | 101 +(2 rows) + +drop function tftest(int); +create or replace function rttest() +returns setof int as $$ +declare rc int; +begin + return query values(10),(20); + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query select * from (values(10),(20)) f(a) where false; + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query execute 'values(10),(20)'; + get diagnostics rc = row_count; + raise notice '% %', found, rc; + return query execute 'select * from (values(10),(20)) f(a) where false'; + get diagnostics rc = row_count; + raise notice '% %', found, rc; +end; +$$ language plpgsql; +select * from rttest() order by 1; +NOTICE: t 2 +NOTICE: f 0 +NOTICE: t 2 +NOTICE: f 0 + rttest +-------- + 10 + 10 + 20 + 20 +(4 rows) + +drop function rttest(); +-- Test for proper cleanup at subtransaction exit. This example +-- exposed a bug in PG 8.2. +CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGER AS $$ +DECLARE + v_var INTEGER; +BEGIN + BEGIN + v_var := (leaker_2(fail)).error_code; + EXCEPTION + WHEN others THEN RETURN 0; + END; + RETURN 1; +END; +$$ LANGUAGE plpgsql; +CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER) + RETURNS RECORD AS $$ +BEGIN + IF fail THEN + RAISE EXCEPTION 'fail ...'; + END IF; + error_code := 1; + new_id := 1; + RETURN; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM leaker_1(false); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function leaker_1(boolean) line 5 during statement block entry +SELECT * FROM leaker_1(true); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function leaker_1(boolean) line 5 during statement block entry +DROP FUNCTION leaker_1(bool); +DROP FUNCTION leaker_2(bool); +-- Test for appropriate cleanup of non-simple expression evaluations +-- (bug in all versions prior to August 2010) +CREATE FUNCTION nonsimple_expr_test() RETURNS text[] AS $$ +DECLARE + arr text[]; + lr text; + i integer; +BEGIN + arr := array[array['foo','bar'], array['baz', 'quux']]; + lr := 'fool'; + i := 1; + -- use sub-SELECTs to make expressions non-simple + arr[(SELECT i)][(SELECT i+1)] := (SELECT lr); + RETURN arr; +END; +$$ LANGUAGE plpgsql; +SELECT nonsimple_expr_test(); + nonsimple_expr_test +------------------------- + {{foo,fool},{baz,quux}} +(1 row) + +DROP FUNCTION nonsimple_expr_test(); +CREATE FUNCTION nonsimple_expr_test() RETURNS integer AS $$ +declare + i integer NOT NULL := 0; +begin + begin + i := (SELECT NULL::integer); -- should throw error + exception + WHEN OTHERS THEN + i := (SELECT 1::integer); + end; + return i; +end; +$$ LANGUAGE plpgsql; +SELECT nonsimple_expr_test(); +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function nonsimple_expr_test() line 5 during statement block entry +DROP FUNCTION nonsimple_expr_test(); +-- +-- Test cases involving recursion and error recovery in simple expressions +-- (bugs in all versions before October 2010). The problems are most +-- easily exposed by mutual recursion between plpgsql and sql functions. +-- +create function recurse(float8) returns float8 as +$$ +begin + if ($1 > 0) then + return sql_recurse($1 - 1); + else + return $1; + end if; +end; +$$ language plpgsql; +-- "limit" is to prevent this from being inlined +create function sql_recurse(float8) returns float8 as +$$ select recurse($1) limit 1; $$ language sql; +select recurse(10); + recurse +--------- + 0 +(1 row) + +create function error1(text) returns text language sql as +$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$; +create function error2(p_name_table text) returns text language plpgsql as $$ +begin + return error1(p_name_table); +end$$; +BEGIN; +create table public.stuffs (stuff text); +SAVEPOINT a; +ERROR: SAVEPOINT is not yet supported. +select error2('nonexistent.stuffs'); +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK TO a; +ERROR: no such savepoint +select error2('public.stuffs'); +ERROR: current transaction is aborted, commands ignored until end of transaction block +rollback; +drop function error2(p_name_table text); +drop function error1(text); +-- Test for proper handling of cast-expression caching +create function sql_to_date(integer) returns date as $$ +select $1::text::date +$$ language sql immutable strict; +create cast (integer as date) with function sql_to_date(integer) as assignment; +create function cast_invoker(integer) returns date as $$ +begin + return $1; +end$$ language plpgsql; +select cast_invoker(20150717); + cast_invoker +-------------- + 07-17-2015 +(1 row) + +select cast_invoker(20150718); -- second call crashed in pre-release 9.5 + cast_invoker +-------------- + 07-18-2015 +(1 row) + +begin; +select cast_invoker(20150717); + cast_invoker +-------------- + 07-17-2015 +(1 row) + +select cast_invoker(20150718); + cast_invoker +-------------- + 07-18-2015 +(1 row) + +savepoint s1; +ERROR: SAVEPOINT is not yet supported. +select cast_invoker(20150718); +ERROR: current transaction is aborted, commands ignored until end of transaction block +select cast_invoker(-1); -- fails +ERROR: current transaction is aborted, commands ignored until end of transaction block +rollback to savepoint s1; +ERROR: no such savepoint +select cast_invoker(20150719); +ERROR: current transaction is aborted, commands ignored until end of transaction block +select cast_invoker(20150720); +ERROR: current transaction is aborted, commands ignored until end of transaction block +commit; +drop function cast_invoker(integer); +drop function sql_to_date(integer) cascade; +NOTICE: drop cascades to cast from integer to date +-- Test handling of cast cache inside DO blocks +-- (to check the original crash case, this must be a cast not previously +-- used in this session) +begin; +do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; +do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$; +end; +-- Test for consistent reporting of error context +create function fail() returns int language plpgsql as $$ +begin + return 1/0; +end +$$; +select fail(); +ERROR: division by zero +CONTEXT: SQL statement "SELECT 1/0" +PL/pgSQL function fail() line 3 at RETURN +select fail(); +ERROR: division by zero +CONTEXT: SQL statement "SELECT 1/0" +PL/pgSQL function fail() line 3 at RETURN +drop function fail(); +-- Test handling of string literals. +set standard_conforming_strings = off; +create or replace function strtest() returns text as $$ +begin + raise notice 'foo\\bar\041baz'; + return 'foo\\bar\041baz'; +end +$$ language plpgsql; +WARNING: nonstandard use of \\ in a string literal +LINE 3: raise notice 'foo\\bar\041baz'; + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 4: return 'foo\\bar\041baz'; + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +WARNING: nonstandard use of \\ in a string literal +LINE 4: return 'foo\\bar\041baz'; + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +select strtest(); +NOTICE: foo\bar!baz +WARNING: nonstandard use of \\ in a string literal +LINE 1: SELECT 'foo\\bar\041baz' + ^ +HINT: Use the escape string syntax for backslashes, e.g., E'\\'. +QUERY: SELECT 'foo\\bar\041baz' +CONTEXT: PL/pgSQL function strtest() line 4 at RETURN + strtest +------------- + foo\bar!baz +(1 row) + +create or replace function strtest() returns text as $$ +begin + raise notice E'foo\\bar\041baz'; + return E'foo\\bar\041baz'; +end +$$ language plpgsql; +select strtest(); +NOTICE: foo\bar!baz + strtest +------------- + foo\bar!baz +(1 row) + +set standard_conforming_strings = on; +create or replace function strtest() returns text as $$ +begin + raise notice 'foo\\bar\041baz\'; + return 'foo\\bar\041baz\'; +end +$$ language plpgsql; +select strtest(); +NOTICE: foo\\bar\041baz\ + strtest +------------------ + foo\\bar\041baz\ +(1 row) + +create or replace function strtest() returns text as $$ +begin + raise notice E'foo\\bar\041baz'; + return E'foo\\bar\041baz'; +end +$$ language plpgsql; +select strtest(); +NOTICE: foo\bar!baz + strtest +------------- + foo\bar!baz +(1 row) + +drop function strtest(); +-- Test anonymous code blocks. +DO $$ +DECLARE r record; +BEGIN + FOR r IN SELECT rtrim(roomno) AS roomno, comment FROM Room ORDER BY roomno + LOOP + RAISE NOTICE '%, %', r.roomno, r.comment; + END LOOP; +END$$; +NOTICE: 001, Entrance +NOTICE: 002, Office +NOTICE: 003, Office +NOTICE: 004, Technical +NOTICE: 101, Office +NOTICE: 102, Conference +NOTICE: 103, Restroom +NOTICE: 104, Technical +NOTICE: 105, Office +NOTICE: 106, Office +-- these are to check syntax error reporting +DO LANGUAGE plpgsql $$begin return 1; end$$; +ERROR: RETURN cannot have a parameter in function returning void +LINE 1: DO LANGUAGE plpgsql $$begin return 1; end$$; + ^ +DO $$ +DECLARE r record; +BEGIN + FOR r IN SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno + LOOP + RAISE NOTICE '%, %', r.roomno, r.comment; + END LOOP; +END$$; +ERROR: column "foo" does not exist +LINE 1: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomn... + ^ +QUERY: SELECT rtrim(roomno) AS roomno, foo FROM Room ORDER BY roomno +CONTEXT: PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows +-- Check handling of errors thrown from/into anonymous code blocks. +do $outer$ +begin + for i in 1..10 loop + begin + execute $ex$ + do $$ + declare x int = 0; + begin + x := 1 / x; + end; + $$; + $ex$; + exception when division_by_zero then + raise notice 'caught division by zero'; + end; + end loop; +end; +$outer$; +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function inline_code_block line 4 during statement block entry +-- Check variable scoping -- a var is not available in its own or prior +-- default expressions. +create function scope_test() returns int as $$ +declare x int := 42; +begin + declare y int := x + 1; + x int := x + 2; + begin + return x * 100 + y; + end; +end; +$$ language plpgsql; +select scope_test(); + scope_test +------------ + 4443 +(1 row) + +drop function scope_test(); +-- Check handling of conflicts between plpgsql vars and table columns. +set plpgsql.variable_conflict = error; +create function conflict_test() returns setof int8_tbl as $$ +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; +select * from conflict_test() order by 1,2; +ERROR: column reference "q1" is ambiguous +LINE 1: select q1,q2 from int8_tbl + ^ +DETAIL: It could refer to either a PL/pgSQL variable or a table column. +QUERY: select q1,q2 from int8_tbl +CONTEXT: PL/pgSQL function conflict_test() line 5 at FOR over SELECT rows +create or replace function conflict_test() returns setof int8_tbl as $$ +#variable_conflict use_variable +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; +-- PGXCTODO: This is failing due to issue 3522907, complicated SELECT queries in plpgsql functions +select * from conflict_test() order by 1,2; + q1 | q2 +----+------------------- + 42 | -4567890123456789 + 42 | 123 + 42 | 456 + 42 | 4567890123456789 + 42 | 4567890123456789 +(5 rows) + +create or replace function conflict_test() returns setof int8_tbl as $$ +#variable_conflict use_column +declare r record; + q1 bigint := 42; +begin + for r in select q1,q2 from int8_tbl loop + return next r; + end loop; +end; +$$ language plpgsql; +select * from conflict_test() order by 1,2; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | -4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 +(5 rows) + +drop function conflict_test(); +-- Check that an unreserved keyword can be used as a variable name +create function unreserved_test() returns int as $$ +declare + forward int := 21; +begin + forward := forward * 2; + return forward; +end +$$ language plpgsql; +select unreserved_test(); + unreserved_test +----------------- + 42 +(1 row) + +create or replace function unreserved_test() returns int as $$ +declare + return int := 42; +begin + return := return + 1; + return return; +end +$$ language plpgsql; +select unreserved_test(); + unreserved_test +----------------- + 43 +(1 row) + +drop function unreserved_test(); +-- +-- Test FOREACH over arrays +-- +create function foreach_test(anyarray) +returns void as $$ +declare x int; +begin + foreach x in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[1,2,3,4]); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[1,2],[3,4]]); +NOTICE: 1 +NOTICE: 2 +NOTICE: 3 +NOTICE: 4 + foreach_test +-------------- + +(1 row) + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +-- should fail +select foreach_test(ARRAY[1,2,3,4]); +ERROR: FOREACH ... SLICE loop variable must be of an array type +CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array +select foreach_test(ARRAY[[1,2],[3,4]]); +ERROR: FOREACH ... SLICE loop variable must be of an array type +CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int[]; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[1,2,3,4]); +NOTICE: {1,2,3,4} + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[1,2],[3,4]]); +NOTICE: {1,2} +NOTICE: {3,4} + foreach_test +-------------- + +(1 row) + +-- higher level of slicing +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int[]; +begin + foreach x slice 2 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +-- should fail +select foreach_test(ARRAY[1,2,3,4]); +ERROR: slice dimension (2) is out of the valid range 0..1 +CONTEXT: PL/pgSQL function foreach_test(anyarray) line 4 at FOREACH over array +-- ok +select foreach_test(ARRAY[[1,2],[3,4]]); +NOTICE: {{1,2},{3,4}} + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[[1,2]],[[3,4]]]); +NOTICE: {{1,2}} +NOTICE: {{3,4}} + foreach_test +-------------- + +(1 row) + +create type xy_tuple AS (x int, y int); +-- iteration over array of records +create or replace function foreach_test(anyarray) +returns void as $$ +declare r record; +begin + foreach r in array $1 + loop + raise notice '%', r; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +NOTICE: (10,20) +NOTICE: (40,69) +NOTICE: (35,78) + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); +NOTICE: (10,20) +NOTICE: (40,69) +NOTICE: (35,78) +NOTICE: (88,76) + foreach_test +-------------- + +(1 row) + +create or replace function foreach_test(anyarray) +returns void as $$ +declare x int; y int; +begin + foreach x, y in array $1 + loop + raise notice 'x = %, y = %', x, y; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +NOTICE: x = 10, y = 20 +NOTICE: x = 40, y = 69 +NOTICE: x = 35, y = 78 + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); +NOTICE: x = 10, y = 20 +NOTICE: x = 40, y = 69 +NOTICE: x = 35, y = 78 +NOTICE: x = 88, y = 76 + foreach_test +-------------- + +(1 row) + +-- slicing over array of composite types +create or replace function foreach_test(anyarray) +returns void as $$ +declare x xy_tuple[]; +begin + foreach x slice 1 in array $1 + loop + raise notice '%', x; + end loop; + end; +$$ language plpgsql; +select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]); +NOTICE: {"(10,20)","(40,69)","(35,78)"} + foreach_test +-------------- + +(1 row) + +select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]); +NOTICE: {"(10,20)","(40,69)"} +NOTICE: {"(35,78)","(88,76)"} + foreach_test +-------------- + +(1 row) + +drop function foreach_test(anyarray); +drop type xy_tuple; +-- +-- Assorted tests for array subscript assignment +-- +create temp table rtype (id int, ar text[]); +create function arrayassign1() returns text[] language plpgsql as $$ +declare + r record; +begin + r := row(12, '{foo,bar,baz}')::rtype; + r.ar[2] := 'replace'; + return r.ar; +end$$; +select arrayassign1(); + arrayassign1 +------------------- + {foo,replace,baz} +(1 row) + +select arrayassign1(); -- try again to exercise internal caching + arrayassign1 +------------------- + {foo,replace,baz} +(1 row) + +create domain orderedarray as int[2] + constraint sorted check (value[1] < value[2]); +select '{1,2}'::orderedarray; + orderedarray +-------------- + {1,2} +(1 row) + +select '{2,1}'::orderedarray; -- fail +ERROR: value for domain orderedarray violates check constraint "sorted" +create function testoa(x1 int, x2 int, x3 int) returns orderedarray +language plpgsql as $$ +declare res orderedarray; +begin + res := array[x1, x2]; + res[2] := x3; + return res; +end$$; +select testoa(1,2,3); + testoa +-------- + {1,3} +(1 row) + +select testoa(1,2,3); -- try again to exercise internal caching + testoa +-------- + {1,3} +(1 row) + +select testoa(2,1,3); -- fail at initial assign +ERROR: value for domain orderedarray violates check constraint "sorted" +CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 4 at assignment +select testoa(1,2,1); -- fail at update +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 that DMLs in a plpgsql function work OK, when subsequent queries need +-- to open new datanode connections +CREATE OR REPLACE FUNCTION TestJoinTempTable_CT() +RETURNS void AS $$ +BEGIN + CREATE TABLE IF NOT EXISTS RealTable(ProductId int, ScenarioId int); + TRUNCATE TABLE RealTable; + + CREATE TABLE IF NOT EXISTS TmpBar(NodeId int) + DISTRIBUTE BY REPLICATION; + CREATE TABLE IF NOT EXISTS TmpFoo(TempId int) + DISTRIBUTE BY REPLICATION; +END ; +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION TestJoinTempTable_INSERT() +RETURNS void AS $$ +BEGIN + INSERT INTO RealTable(ProductId, ScenarioId) + SELECT generate_series(1,1000) as ProductId, (random() * 100)::int as ScenarioId; + + INSERT INTO TmpBar(NodeId) + SELECT generate_series(1,1000); + RAISE INFO 'number of existing rows in RealTable - %', (SELECT count(*) FROM RealTable); + RAISE INFO 'number of existing rows in TmpBar - %', (SELECT count(*) FROM TmpBar); + RAISE INFO 'number of existing rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); + INSERT INTO TmpFoo(TempId) + SELECT DISTINCT(PR.ProductId) + FROM RealTable AS PR + JOIN TmpBar tmp1 ON PR.ProductId = tmp1.NodeId; + + RAISE INFO 'number of rows produced by query - %', + (SELECT COUNT(DISTINCT(PR.ProductId)) + FROM RealTable AS PR + JOIN TmpBar tmp1 ON PR.ProductId = tmp1.NodeId); + RAISE INFO 'number of rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); + RAISE INFO 'number of existing rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); + RAISE INFO 'number of existing rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); +END ; +$$ LANGUAGE plpgsql; +SELECT TestJoinTempTable_CT(); + testjointemptable_ct +---------------------- + +(1 row) + +SELECT TestJoinTempTable_INSERT(); +INFO: number of existing rows in RealTable - 1000 +INFO: number of existing rows in TmpBar - 1000 +INFO: number of existing rows in TmpFoo - 0 +INFO: number of rows produced by query - 1000 +INFO: number of rows in TmpFoo - 1000 +INFO: number of existing rows in TmpFoo - 1000 +INFO: number of existing rows in TmpFoo - 1000 + testjointemptable_insert +-------------------------- + +(1 row) + +DROP TABLE RealTable; +DROP TABLE TmpBar; +DROP TABLE TmpFoo; +CREATE OR REPLACE FUNCTION TestJoinTempTable() +RETURNS void AS $$ +BEGIN + CREATE TABLE IF NOT EXISTS RealTable(ProductId int, ScenarioId int); + TRUNCATE TABLE RealTable; + + CREATE TEMPORARY TABLE IF NOT EXISTS TmpBar(NodeId int) + DISTRIBUTE BY REPLICATION; + CREATE TEMPORARY TABLE IF NOT EXISTS TmpFoo(TempId int) + DISTRIBUTE BY REPLICATION; + + INSERT INTO RealTable(ProductId, ScenarioId) + SELECT generate_series(1,1000) as ProductId, (random() * 100)::int as ScenarioId; + + INSERT INTO TmpBar(NodeId) + SELECT generate_series(1,1000); + + INSERT INTO TmpFoo(TempId) + SELECT DISTINCT(PR.ProductId) + FROM RealTable AS PR + JOIN TmpBar tmp1 ON PR.ProductId = tmp1.NodeId; +END ; +$$ LANGUAGE plpgsql; +SELECT TestJoinTempTable(); + testjointemptable +------------------- + +(1 row) + +-- Multiple invokations of the function showed interesting issues with command +-- passdown. So add that to the test case +SELECT TestJoinTempTable(); +NOTICE: relation "realtable" already exists, skipping +CONTEXT: SQL statement "CREATE TABLE IF NOT EXISTS RealTable(ProductId int, ScenarioId int)" +PL/pgSQL function testjointemptable() line 3 at SQL statement +NOTICE: relation "tmpbar" already exists, skipping +CONTEXT: SQL statement "CREATE TEMPORARY TABLE IF NOT EXISTS TmpBar(NodeId int) + DISTRIBUTE BY REPLICATION" +PL/pgSQL function testjointemptable() line 6 at SQL statement +NOTICE: relation "tmpfoo" already exists, skipping +CONTEXT: SQL statement "CREATE TEMPORARY TABLE IF NOT EXISTS TmpFoo(TempId int) + DISTRIBUTE BY REPLICATION" +PL/pgSQL function testjointemptable() line 8 at SQL statement + testjointemptable +------------------- + +(1 row) + +SELECT TestJoinTempTable(); +NOTICE: relation "realtable" already exists, skipping +CONTEXT: SQL statement "CREATE TABLE IF NOT EXISTS RealTable(ProductId int, ScenarioId int)" +PL/pgSQL function testjointemptable() line 3 at SQL statement +NOTICE: relation "tmpbar" already exists, skipping +CONTEXT: SQL statement "CREATE TEMPORARY TABLE IF NOT EXISTS TmpBar(NodeId int) + DISTRIBUTE BY REPLICATION" +PL/pgSQL function testjointemptable() line 6 at SQL statement +NOTICE: relation "tmpfoo" already exists, skipping +CONTEXT: SQL statement "CREATE TEMPORARY TABLE IF NOT EXISTS TmpFoo(TempId int) + DISTRIBUTE BY REPLICATION" +PL/pgSQL function testjointemptable() line 8 at SQL statement + testjointemptable +------------------- + +(1 row) + +DROP TABLE RealTable; +DROP TABLE TmpBar; +DROP TABLE TmpFoo; - -- access to call stack ++-- ++-- Test handling of expanded arrays ++-- ++create function returns_rw_array(int) returns int[] ++language plpgsql as $$ ++ declare r int[]; ++ begin r := array[$1, $1]; return r; end; ++$$ stable; ++create function consumes_rw_array(int[]) returns int ++language plpgsql as $$ ++ begin return $1[1]; end; ++$$ stable; ++-- bug #14174 ++explain (verbose, costs off) ++select i, a from ++ (select returns_rw_array(1) as a offset 0) ss, ++ lateral consumes_rw_array(a) i; ++ QUERY PLAN ++----------------------------------------------------------------- ++ Nested Loop ++ Output: i.i, (returns_rw_array(1)) ++ -> Result ++ Output: returns_rw_array(1) ++ -> Function Scan on public.consumes_rw_array i ++ Output: i.i ++ Function Call: consumes_rw_array((returns_rw_array(1))) ++(7 rows) ++ ++select i, a from ++ (select returns_rw_array(1) as a offset 0) ss, ++ lateral consumes_rw_array(a) i; ++ i | a ++---+------- ++ 1 | {1,1} ++(1 row) ++ ++explain (verbose, costs off) ++select consumes_rw_array(a), a from returns_rw_array(1) a; ++ QUERY PLAN ++-------------------------------------------- ++ Function Scan on public.returns_rw_array a ++ Output: consumes_rw_array(a), a ++ Function Call: returns_rw_array(1) ++(3 rows) ++ ++select consumes_rw_array(a), a from returns_rw_array(1) a; ++ consumes_rw_array | a ++-------------------+------- ++ 1 | {1,1} ++(1 row) ++ ++explain (verbose, costs off) ++select consumes_rw_array(a), a from ++ (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); ++ QUERY PLAN ++--------------------------------------------------------------------- ++ Values Scan on "*VALUES*" ++ Output: consumes_rw_array("*VALUES*".column1), "*VALUES*".column1 ++(2 rows) ++ ++select consumes_rw_array(a), a from ++ (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); ++ consumes_rw_array | a ++-------------------+------- ++ 1 | {1,1} ++ 2 | {2,2} ++(2 rows) ++ ++-- ++-- Test access to call stack ++-- +create function inner_func(int) +returns int as $$ +declare _context text; +begin + get diagnostics _context = pg_context; + raise notice '***%***', _context; + -- lets do it again, just for fun.. + get diagnostics _context = pg_context; + raise notice '***%***', _context; + raise notice 'lets make sure we didnt break anything'; + return 2 * $1; +end; +$$ language plpgsql; +create or replace function outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into inner_func()'; + myresult := inner_func($1); + raise notice 'inner_func() done'; + return myresult; +end; +$$ language plpgsql; +create or replace function outer_outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into outer_func()'; + myresult := outer_func($1); + raise notice 'outer_func() done'; + return myresult; +end; +$$ language plpgsql; +select outer_outer_func(10); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: lets make sure we didnt break anything +CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: inner_func() done +CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: outer_func() done + outer_outer_func +------------------ + 20 +(1 row) + +-- repeated call should to work +select outer_outer_func(20); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: ***PL/pgSQL function inner_func(integer) line 4 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: ***PL/pgSQL function inner_func(integer) line 7 at GET DIAGNOSTICS +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment*** +CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: lets make sure we didnt break anything +CONTEXT: PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: inner_func() done +CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment +NOTICE: outer_func() done + outer_outer_func +------------------ + 40 +(1 row) + +drop function outer_outer_func(int); +drop function outer_func(int); +drop function inner_func(int); +-- access to call stack from exception +create function inner_func(int) +returns int as $$ +declare + _context text; + sx int := 5; +begin + begin + perform sx / 0; + exception + when division_by_zero then + get diagnostics _context = pg_context; + raise notice '***%***', _context; + end; + + -- lets do it again, just for fun.. + get diagnostics _context = pg_context; + raise notice '***%***', _context; + raise notice 'lets make sure we didnt break anything'; + return 2 * $1; +end; +$$ language plpgsql; +create or replace function outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into inner_func()'; + myresult := inner_func($1); + raise notice 'inner_func() done'; + return myresult; +end; +$$ language plpgsql; +create or replace function outer_outer_func(int) +returns int as $$ +declare + myresult int; +begin + raise notice 'calling down into outer_func()'; + myresult := outer_func($1); + raise notice 'outer_func() done'; + return myresult; +end; +$$ language plpgsql; +select outer_outer_func(10); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function inner_func(integer) line 6 during statement block entry +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +-- repeated call should to work +select outer_outer_func(20); +NOTICE: calling down into outer_func() +NOTICE: calling down into inner_func() +CONTEXT: PL/pgSQL function outer_outer_func(integer) line 6 at assignment +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function inner_func(integer) line 6 during statement block entry +PL/pgSQL function outer_func(integer) line 6 at assignment +PL/pgSQL function outer_outer_func(integer) line 6 at assignment +drop function outer_outer_func(int); +drop function outer_func(int); +drop function inner_func(int); +-- +-- Test ASSERT +-- +do $$ +begin + assert 1=1; -- should succeed +end; +$$; +do $$ +begin + assert 1=0; -- should fail +end; +$$; +ERROR: assertion failed +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +do $$ +begin + assert NULL; -- should fail +end; +$$; +ERROR: assertion failed +CONTEXT: PL/pgSQL function inline_code_block line 3 at ASSERT +-- check controlling GUC +set plpgsql.check_asserts = off; +do $$ +begin + assert 1=0; -- won't be tested +end; +$$; +reset plpgsql.check_asserts; +-- test custom message +do $$ +declare var text := 'some value'; +begin + assert 1=0, format('assertion failed, var = "%s"', var); +end; +$$; +ERROR: assertion failed, var = "some value" +CONTEXT: PL/pgSQL function inline_code_block line 4 at ASSERT +-- ensure assertions are not trapped by 'others' +do $$ +begin + assert 1=0, 'unhandled assertion'; +exception when others then + null; -- do nothing +end; +$$; +ERROR: Internal subtransactions not supported in Postgres-XL +CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block entry diff --cc src/test/regress/expected/rowsecurity.out index 626a6f6edf,9931fd5944..f4e7c4964d --- a/src/test/regress/expected/rowsecurity.out +++ b/src/test/regress/expected/rowsecurity.out @@@ -645,12 -664,23 +645,12 @@@ EXPLAIN (COSTS OFF) SELECT a, b, oid FR RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => cde -NOTICE: f_leak => def -NOTICE: f_leak => xxx -NOTICE: f_leak => yyy -NOTICE: f_leak => zzz a | b ---+----- - 1 | aaa + 1 | aba 2 | bbb 3 | ccc - 4 | ddd + 4 | dad 1 | abc 2 | bcd 3 | cde @@@ -679,12 -706,23 +679,12 @@@ EXPLAIN (COSTS OFF) SELECT * FROM t1 WH SET SESSION AUTHORIZATION rls_regress_exempt_user; SET row_security TO OFF; SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => cde -NOTICE: f_leak => def -NOTICE: f_leak => xxx -NOTICE: f_leak => yyy -NOTICE: f_leak => zzz a | b ---+----- - 1 | aaa + 1 | aba 2 | bbb 3 | ccc - 4 | ddd + 4 | dad 1 | abc 2 | bcd 3 | cde @@@ -928,12 -952,23 +928,12 @@@ EXPLAIN (COSTS OFF) EXECUTE p1(2) RESET SESSION AUTHORIZATION; SET row_security TO OFF; SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => cde -NOTICE: f_leak => def -NOTICE: f_leak => xxx -NOTICE: f_leak => yyy -NOTICE: f_leak => zzz a | b ---+----- - 1 | aaa + 1 | aba 2 | bbb 3 | ccc - 4 | ddd + 4 | dad 1 | abc 2 | bcd 3 | cde @@@ -1090,20 -1120,30 +1090,20 @@@ UPDATE t1 SET b = b WHERE f_leak(b) RET a | b ---+------------- 2 | bbbbbb_updt - 4 | daddad_updt 2 | bcdbcd - 4 | defdef 2 | yyyyyy - 4 | dddddd_updt ++ 4 | daddad_updt + 4 | defdef (5 rows) UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1; oid | a | b | t1 -----+---+-------------+----------------- 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt) - 104 | 4 | daddad_updt | (4,daddad_updt) 202 | 2 | bcdbcd | (2,bcdbcd) - 204 | 4 | defdef | (4,defdef) 302 | 2 | yyyyyy | (2,yyyyyy) - 104 | 4 | dddddd_updt | (4,dddddd_updt) ++ 104 | 4 | daddad_updt | (4,daddad_updt) + 204 | 4 | defdef | (4,defdef) (5 rows) -- updates with from clause @@@ -1590,9 -1777,11 +1590,9 @@@ EXPLAIN (COSTS OFF) EXECUTE plancache_t SET SESSION AUTHORIZATION rls_regress_user2; SELECT * FROM z1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => ccc a | b ---+----- - 1 | aaa + 1 | aba 3 | ccc (2 rows) @@@ -1650,9 -1833,11 +1650,9 @@@ EXPLAIN (COSTS OFF) EXECUTE plancache_t SET ROLE rls_regress_group2; SELECT * FROM z1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => ccc a | b ---+----- - 1 | aaa + 1 | aba 3 | ccc (2 rows) @@@ -1718,12 -1897,16 +1718,12 @@@ GRANT SELECT ON rls_view TO rls_regress -- Query as role that is not owner of view or table. Should return all records. SET SESSION AUTHORIZATION rls_regress_user1; SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad a | b ---+----- - 1 | aaa + 1 | aba 2 | bbb 3 | ccc - 4 | ddd + 4 | dad (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM rls_view; @@@ -1737,12 -1919,16 +1737,12 @@@ -- Query as view/table owner. Should return all records. SET SESSION AUTHORIZATION rls_regress_user0; SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad a | b ---+----- - 1 | aaa + 1 | aba 2 | bbb 3 | ccc - 4 | ddd + 4 | dad (4 rows) EXPLAIN (COSTS OFF) SELECT * FROM rls_view; diff --cc src/test/regress/expected/select_into.out index 0ba5b716e3,6be8129f83..8795525c75 --- a/src/test/regress/expected/select_into.out +++ b/src/test/regress/expected/select_into.out @@@ -50,6 -50,44 +50,44 @@@ DETAIL: drop cascades to table selinto drop cascades to table selinto_schema.tmp2 drop cascades to table selinto_schema.tmp3 DROP USER selinto_user; + -- Tests for WITH NO DATA and column name consistency + CREATE TABLE ctas_base (i int, j int); + INSERT INTO ctas_base VALUES (1, 2); + CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base; -- Error -ERROR: too many column names were specified ++ERROR: CREATE TABLE AS specifies too many column names + CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base WITH NO DATA; -- Error -ERROR: too many column names were specified ++ERROR: CREATE TABLE AS specifies too many column names + CREATE TABLE ctas_nodata (ii, jj) AS SELECT i, j FROM ctas_base; -- OK + CREATE TABLE ctas_nodata_2 (ii, jj) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK + CREATE TABLE ctas_nodata_3 (ii) AS SELECT i, j FROM ctas_base; -- OK + CREATE TABLE ctas_nodata_4 (ii) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK + SELECT * FROM ctas_nodata; + ii | jj + ----+---- + 1 | 2 + (1 row) + + SELECT * FROM ctas_nodata_2; + ii | jj + ----+---- + (0 rows) + + SELECT * FROM ctas_nodata_3; + ii | j + ----+--- + 1 | 2 + (1 row) + + SELECT * FROM ctas_nodata_4; + ii | j + ----+--- + (0 rows) + + DROP TABLE ctas_base; + DROP TABLE ctas_nodata; + DROP TABLE ctas_nodata_2; + DROP TABLE ctas_nodata_3; + DROP TABLE ctas_nodata_4; -- -- CREATE TABLE AS/SELECT INTO as last command in a SQL function -- have been known to cause problems diff --cc src/test/regress/sql/case.sql index c311e8151b,b2377e4610..ed76878217 --- a/src/test/regress/sql/case.sql +++ b/src/test/regress/sql/case.sql @@@ -162,8 -154,46 +162,46 @@@ UPDATE CASE_TB FROM CASE2_TBL b WHERE j = -CASE_TBL.i; -SELECT * FROM CASE_TBL; +SELECT * FROM CASE_TBL ORDER BY i, f; + -- + -- Nested CASE expressions + -- + + -- This test exercises a bug caused by aliasing econtext->caseValue_isNull + -- with the isNull argument of the inner CASE's ExecEvalCase() call. After + -- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, + -- the isNull flag for the case test value incorrectly became true, causing + -- the third WHEN-clause not to match. The volatile function calls are needed + -- to prevent constant-folding in the planner, which would hide the bug. + + CREATE FUNCTION vol(text) returns text as + 'begin return $1; end' language plpgsql volatile; + + SELECT CASE + (CASE vol('bar') + WHEN 'foo' THEN 'it was foo!' + WHEN vol(null) THEN 'null input' + WHEN 'bar' THEN 'it was bar!' END + ) + WHEN 'it was foo!' THEN 'foo recognized' + WHEN 'it was bar!' THEN 'bar recognized' + ELSE 'unrecognized' END; + + -- In this case, we can't inline the SQL function without confusing things. + CREATE DOMAIN foodomain AS text; + + CREATE FUNCTION volfoo(text) returns foodomain as + 'begin return $1::foodomain; end' language plpgsql volatile; + + CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as + 'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql; + + CREATE OPERATOR = (procedure = inline_eq, + leftarg = foodomain, rightarg = foodomain); + + SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END; + -- -- Clean up -- diff --cc src/test/regress/sql/insert_conflict.sql index e29e6aecc5,190c605562..2aba222966 --- a/src/test/regress/sql/insert_conflict.sql +++ b/src/test/regress/sql/insert_conflict.sql @@@ -263,6 -261,28 +263,28 @@@ insert into syscolconflicttest values ( insert into syscolconflicttest values (1) on conflict (key) do update set data = excluded.oid::text; drop table syscolconflicttest; + -- + -- Previous tests all managed to not test any expressions requiring + -- planner preprocessing ... + -- -create table insertconflict (a bigint, b bigint); ++create table insertconflict (a bigint, b bigint) distribute by replication; + + create unique index insertconflicti1 on insertconflict(coalesce(a, 0)); + + create unique index insertconflicti2 on insertconflict(b) + where coalesce(a, 1) > 0; + + insert into insertconflict values (1, 2) + on conflict (coalesce(a, 0)) do nothing; + + insert into insertconflict values (1, 2) + on conflict (b) where coalesce(a, 1) > 0 do nothing; + + insert into insertconflict values (1, 2) + on conflict (b) where coalesce(a, 1) > 1 do nothing; + + drop table insertconflict; + -- ****************************************************************** -- * * diff --cc src/test/regress/sql/matview.sql index 2ba84e9244,a37bf5ac5b..ec66be15a9 --- a/src/test/regress/sql/matview.sql +++ b/src/test/regress/sql/matview.sql @@@ -170,17 -173,36 +170,35 @@@ UPDATE boxes SET b = '(2,2),(1,1)' WHER SELECT * FROM boxmv ORDER BY id; DROP TABLE boxes CASCADE; ---- make sure that column names are handled correctly - CREATE TABLE v (i int, j int); - CREATE MATERIALIZED VIEW mv_v (ii) AS SELECT i, j AS jj FROM v; - ALTER TABLE v RENAME COLUMN i TO x; - INSERT INTO v values (1, 2); - CREATE UNIQUE INDEX mv_v_ii ON mv_v (ii); - REFRESH MATERIALIZED VIEW mv_v; - UPDATE v SET j = 3 WHERE x = 1; - SELECT * FROM v; - SELECT * FROM mv_v; - DROP TABLE v CASCADE; ++ + CREATE TABLE mvtest_v (i int, j int); + CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error + CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok + CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok + CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error + CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok + CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok + ALTER TABLE mvtest_v RENAME COLUMN i TO x; + INSERT INTO mvtest_v values (1, 2); + CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); + REFRESH MATERIALIZED VIEW mvtest_mv_v; + UPDATE mvtest_v SET j = 3 WHERE x = 1; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; ++-- REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; + REFRESH MATERIALIZED VIEW mvtest_mv_v_2; + REFRESH MATERIALIZED VIEW mvtest_mv_v_3; + REFRESH MATERIALIZED VIEW mvtest_mv_v_4; + SELECT * FROM mvtest_v; + SELECT * FROM mvtest_mv_v; + SELECT * FROM mvtest_mv_v_2; + SELECT * FROM mvtest_mv_v_3; + SELECT * FROM mvtest_mv_v_4; + DROP TABLE mvtest_v CASCADE; + + -- make sure that create WITH NO DATA does not plan the query (bug #13907) + create materialized view mvtest_error as select 1/0 as x; -- fail + create materialized view mvtest_error as select 1/0 as x with no data; + refresh materialized view mvtest_error; -- fail here + drop materialized view mvtest_error; -- make sure that matview rows can be referenced as source rows (bug #9398) CREATE TABLE v AS SELECT generate_series(1,10) AS a; diff --cc src/test/regress/sql/plpgsql.sql index 94a0c2ce5e,a375394bbc..f41ee0e4da --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@@ -4204,90 -4199,49 +4204,131 @@@ select testoa(1,2,1); -- fail at updat drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int); +-- Check that DMLs in a plpgsql function work OK, when subsequent queries need +-- to open new datanode connections +CREATE OR REPLACE FUNCTION TestJoinTempTable_CT() +RETURNS void AS $$ +BEGIN + CREATE TABLE IF NOT EXISTS RealTable(ProductId int, ScenarioId int); + TRUNCATE TABLE RealTable; + + CREATE TABLE IF NOT EXISTS TmpBar(NodeId int) + DISTRIBUTE BY REPLICATION; + CREATE TABLE IF NOT EXISTS TmpFoo(TempId int) + DISTRIBUTE BY REPLICATION; +END ; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION TestJoinTempTable_INSERT() +RETURNS void AS $$ +BEGIN + INSERT INTO RealTable(ProductId, ScenarioId) + SELECT generate_series(1,1000) as ProductId, (random() * 100)::int as ScenarioId; + + INSERT INTO TmpBar(NodeId) + SELECT generate_series(1,1000); + RAISE INFO 'number of existing rows in RealTable - %', (SELECT count(*) FROM RealTable); + RAISE INFO 'number of existing rows in TmpBar - %', (SELECT count(*) FROM TmpBar); + RAISE INFO 'number of existing rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); + INSERT INTO TmpFoo(TempId) + SELECT DISTINCT(PR.ProductId) + FROM RealTable AS PR + JOIN TmpBar tmp1 ON PR.ProductId = tmp1.NodeId; + + RAISE INFO 'number of rows produced by query - %', + (SELECT COUNT(DISTINCT(PR.ProductId)) + FROM RealTable AS PR + JOIN TmpBar tmp1 ON PR.ProductId = tmp1.NodeId); + RAISE INFO 'number of rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); + RAISE INFO 'number of existing rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); + RAISE INFO 'number of existing rows in TmpFoo - %', (SELECT count(*) FROM TmpFoo); +END ; +$$ LANGUAGE plpgsql; + +SELECT TestJoinTempTable_CT(); +SELECT TestJoinTempTable_INSERT(); + +DROP TABLE RealTable; +DROP TABLE TmpBar; +DROP TABLE TmpFoo; + +CREATE OR REPLACE FUNCTION TestJoinTempTable() +RETURNS void AS $$ +BEGIN + CREATE TABLE IF NOT EXISTS RealTable(ProductId int, ScenarioId int); + TRUNCATE TABLE RealTable; + + CREATE TEMPORARY TABLE IF NOT EXISTS TmpBar(NodeId int) + DISTRIBUTE BY REPLICATION; + CREATE TEMPORARY TABLE IF NOT EXISTS TmpFoo(TempId int) + DISTRIBUTE BY REPLICATION; + + INSERT INTO RealTable(ProductId, ScenarioId) + SELECT generate_series(1,1000) as ProductId, (random() * 100)::int as ScenarioId; + + INSERT INTO TmpBar(NodeId) + SELECT generate_series(1,1000); + + INSERT INTO TmpFoo(TempId) + SELECT DISTINCT(PR.ProductId) + FROM RealTable AS PR + JOIN TmpBar tmp1 ON PR.ProductId = tmp1.NodeId; +END ; +$$ LANGUAGE plpgsql; + +SELECT TestJoinTempTable(); + +-- Multiple invokations of the function showed interesting issues with command +-- passdown. So add that to the test case +SELECT TestJoinTempTable(); +SELECT TestJoinTempTable(); + +DROP TABLE RealTable; +DROP TABLE TmpBar; +DROP TABLE TmpFoo; - -- access to call stack + -- + -- Test handling of expanded arrays + -- + + create function returns_rw_array(int) returns int[] + language plpgsql as $$ + declare r int[]; + begin r := array[$1, $1]; return r; end; + $$ stable; + + create function consumes_rw_array(int[]) returns int + language plpgsql as $$ + begin return $1[1]; end; + $$ stable; + + -- bug #14174 + explain (verbose, costs off) + select i, a from + (select returns_rw_array(1) as a offset 0) ss, + lateral consumes_rw_array(a) i; + + select i, a from + (select returns_rw_array(1) as a offset 0) ss, + lateral consumes_rw_array(a) i; + + explain (verbose, costs off) + select consumes_rw_array(a), a from returns_rw_array(1) a; + + select consumes_rw_array(a), a from returns_rw_array(1) a; + + explain (verbose, costs off) + select consumes_rw_array(a), a from + (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); + + select consumes_rw_array(a), a from + (values (returns_rw_array(1)), (returns_rw_array(2))) v(a); + + + -- + -- Test access to call stack + -- + create function inner_func(int) returns int as $$ declare _context text;