On 10/31/16 3:24 PM, Jim Nasby wrote:
This patch increases test coverage for pltcl, from 70% to 83%. Aside
from that, the work on this uncovered 2 new bugs (the trigger return one
I just submitted, as well as a bug in the SRF/composite patch).
Rebased patch attached. Test coverage is now at 85% (by line count).
Original patch by Karl Lehenbauer. Work sponsored by FlightAware.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)
diff --cc src/pl/tcl/expected/pltcl_queries.out
index 3a9fef3447,9e3a0dcd0b..0000000000
--- a/src/pl/tcl/expected/pltcl_queries.out
+++ b/src/pl/tcl/expected/pltcl_queries.out
@@@ -288,6 -350,20 +350,22 @@@ select tcl_argisnull(null)
t
(1 row)
+ -- should error
+ insert into trigger_test(test_argisnull) values(true);
+ NOTICE: NEW: {}
+ NOTICE: OLD: {}
+ NOTICE: TG_level: STATEMENT
+ NOTICE: TG_name: statement_trigger
+ NOTICE: TG_op: INSERT
+ NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+ NOTICE: TG_relid: bogus:12345
+ NOTICE: TG_table_name: trigger_test
+ NOTICE: TG_table_schema: public
+ NOTICE: TG_when: BEFORE
+ NOTICE: args: {42 {statement trigger}}
+ ERROR: argisnull cannot be used in triggers
++select trigger_data();
++ERROR: trigger functions can only be called as triggers
-- Test spi_lastoid primitive
create temp table t1 (f1 int);
select tcl_lastoid('t1');
@@@ -303,64 -379,216 +381,277 @@@ select tcl_lastoid('t2') > 0
t
(1 row)
--- Test quote
+-- test some error cases
- CREATE FUNCTION tcl_error(OUT a int, OUT b int) AS $$return {$$ LANGUAGE
pltcl;
- SELECT tcl_error();
++create function tcl_error(out a int, out b int) as $$return {$$ language
pltcl;
++select tcl_error();
+ERROR: missing close-brace
- CREATE FUNCTION bad_record(OUT a text, OUT b text) AS $$return [list a]$$
LANGUAGE pltcl;
- SELECT bad_record();
++create function bad_record(out a text, out b text) as $$return [list a]$$
language pltcl;
++select bad_record();
+ERROR: column name/value list must have even number of elements
- CREATE FUNCTION bad_field(OUT a text, OUT b text) AS $$return [list a 1 b 2
cow 3]$$ LANGUAGE pltcl;
- SELECT bad_field();
++create function bad_field(out a text, out b text) as $$return [list a 1 b 2
cow 3]$$ language pltcl;
++select bad_field();
+ERROR: column name/value list contains nonexistent column name "cow"
+-- test compound return
+select * from tcl_test_cube_squared(5);
+ squared | cubed
+---------+-------
+ 25 | 125
+(1 row)
+
- -- test SRF
++-- test srf
+select * from tcl_test_squared_rows(0,5);
+ x | y
+---+----
+ 0 | 0
+ 1 | 1
+ 2 | 4
+ 3 | 9
+ 4 | 16
+(5 rows)
+
+select * from tcl_test_sequence(0,5) as a;
+ a
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+(5 rows)
+
+select 1, tcl_test_sequence(0,5);
+ ?column? | tcl_test_sequence
+----------+-------------------
+ 1 | 0
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+(5 rows)
+
- CREATE FUNCTION non_srf() RETURNS int AS $$return_next 1$$ LANGUAGE pltcl;
++create function non_srf() returns int as $$return_next 1$$ language pltcl;
+select non_srf();
+ERROR: return_next cannot be used in non-set-returning functions
- CREATE FUNCTION bad_record_srf(OUT a text, OUT b text) RETURNS SETOF record
AS $$
++create function bad_record_srf(out a text, out b text) returns setof record
as $$
+return_next [list a]
- $$ LANGUAGE pltcl;
- SELECT bad_record_srf();
++$$ language pltcl;
++select bad_record_srf();
+ERROR: column name/value list must have even number of elements
- CREATE FUNCTION bad_field_srf(OUT a text, OUT b text) RETURNS SETOF record AS
$$
++create function bad_field_srf(out a text, out b text) returns setof record as
$$
+return_next [list a 1 b 2 cow 3]
- $$ LANGUAGE pltcl;
- SELECT bad_field_srf();
++$$ language pltcl;
++select bad_field_srf();
+ERROR: column name/value list contains nonexistent column name "cow"
++-- test quote
+ select tcl_eval('quote foo bar');
+ ERROR: wrong # args: should be "quote string"
+ select tcl_eval('quote [format %c 39]');
+ tcl_eval
+ ----------
+ ''
+ (1 row)
+
+ select tcl_eval('quote [format %c 92]');
+ tcl_eval
+ ----------
+ \\
+ (1 row)
+
+ -- Test argisnull
+ select tcl_eval('argisnull');
+ ERROR: wrong # args: should be "argisnull argno"
+ select tcl_eval('argisnull 14');
+ ERROR: argno out of range
+ select tcl_eval('argisnull abc');
+ ERROR: expected integer but got "abc"
+ -- Test return_null
+ select tcl_eval('return_null 14');
+ ERROR: wrong # args: should be "return_null "
+ -- should error
+ insert into trigger_test(test_return_null) values(true);
+ NOTICE: NEW: {}
+ NOTICE: OLD: {}
+ NOTICE: TG_level: STATEMENT
+ NOTICE: TG_name: statement_trigger
+ NOTICE: TG_op: INSERT
+ NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+ NOTICE: TG_relid: bogus:12345
+ NOTICE: TG_table_name: trigger_test
+ NOTICE: TG_table_schema: public
+ NOTICE: TG_when: BEFORE
+ NOTICE: args: {42 {statement trigger}}
+ ERROR: return_null cannot be used in triggers
+ -- Test spi_exec
+ select tcl_eval('spi_exec');
+ ERROR: wrong # args: should be "spi_exec ?-count n? ?-array name? query
?loop body?"
+ select tcl_eval('spi_exec -count');
+ ERROR: missing argument to -count or -array
+ select tcl_eval('spi_exec -array');
+ ERROR: missing argument to -count or -array
+ select tcl_eval('spi_exec -count abc');
+ ERROR: expected integer but got "abc"
+ select tcl_eval('spi_exec query loop body toomuch');
+ ERROR: wrong # args: should be "query ?loop body?"
+ select tcl_eval('spi_exec "begin; rollback;"');
-ERROR: bad option "begin; rollback;": must be -array or -countpltcl:
SPI_execute failed: SPI_ERROR_TRANSACTION
++ERROR: pltcl: SPI_execute failed: SPI_ERROR_TRANSACTION
+ -- Test spi_execp
+ select tcl_eval('spi_execp');
+ ERROR: missing argument to -count or -array
+ select tcl_eval('spi_execp -count');
+ ERROR: missing argument to -array, -count or -nulls
+ select tcl_eval('spi_execp -array');
+ ERROR: missing argument to -array, -count or -nulls
+ select tcl_eval('spi_execp -count abc');
+ ERROR: expected integer but got "abc"
+ select tcl_eval('spi_execp -nulls');
+ ERROR: missing argument to -array, -count or -nulls
+ select tcl_eval('spi_execp ""');
-ERROR: bad option "": must be -array, -count, or -nullsinvalid queryid ''
++ERROR: invalid queryid ''
+ -- test spi_prepare
+ select tcl_eval('spi_prepare');
+ ERROR: wrong # args: should be "spi_prepare query argtypes"
+ select tcl_eval('spi_prepare a b');
+ ERROR: type "b" does not exist
+ select tcl_eval('spi_prepare a "b {"');
+ ERROR: unmatched open brace in list
+ select tcl_error_handling_test($tcl${ spi_prepare "moo" }$tcl$);
+ tcl_error_handling_test
+ ------------------------------
+ COMMAND: spi_prepare "moo" +
+ POSTGRES: 'POSTGRES' +
+ TCL: LOOKUP +
+ funcname: 'funcname' +
+ lineno: 'lineno'
+ (1 row)
+
+ -- test full error text
+ select tcl_error_handling_test($tcl$
+ spi_exec "DO $$
+ BEGIN
+ RAISE 'message'
+ USING HINT = 'hint'
+ , DETAIL = 'detail'
+ , SCHEMA = 'schema'
+ , TABLE = 'table'
+ , COLUMN = 'column'
+ , CONSTRAINT = 'constraint'
+ , DATATYPE = 'datatype'
+ ;
+ END$$;"
+ $tcl$);
+ tcl_error_handling_test
+ --------------------------------------------------------------
+ POSTGRES: 'POSTGRES' +
+ SQLSTATE: P0001 +
+ column: column +
+ condition: raise_exception +
+ constraint: constraint +
+ context: PL/pgSQL function inline_code_block line 3 at RAISE+
+ SQL statement "DO $$ +
+ BEGIN +
+ RAISE 'message' +
+ USING HINT = 'hint' +
+ , DETAIL = 'detail' +
+ , SCHEMA = 'schema' +
+ , TABLE = 'table' +
+ , COLUMN = 'column' +
+ , CONSTRAINT = 'constraint' +
+ , DATATYPE = 'datatype' +
+ ; +
+ END$$;" +
+ datatype: datatype +
+ detail: detail +
+ filename: pl_exec.c +
+ funcname: 'funcname' +
+ hint: hint +
+ lineno: 'lineno' +
+ message: message +
+ schema: schema +
+ table: table
+ (1 row)
+
+ -- test elog
+ select tcl_eval('elog');
+ ERROR: wrong # args: should be "elog level msg"
+ select tcl_eval('elog foo bar');
+ ERROR: bad priority "foo": must be DEBUG, LOG, INFO, NOTICE, WARNING, ERROR,
or FATAL
+ -- test forced error
+ select tcl_eval('error "forced error"');
+ ERROR: forced error
+ select tcl_eval('unset -nocomplain ::tcl_vwait; after 100 {set ::tcl_vwait
1}; vwait ::tcl_vwait; unset -nocomplain ::tcl_vwait');
+ tcl_eval
+ ----------
+
+ (1 row)
+
+ -- test loop control
+ select tcl_spi_exec(true, 'break');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: break
+ NOTICE: end of function
+ tcl_spi_exec
+ --------------
+
+ (1 row)
+
+ select tcl_spi_exec(true, 'continue');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: continue
+ NOTICE: attnum 3, attname reltype
+ NOTICE: end of function
+ tcl_spi_exec
+ --------------
+
+ (1 row)
+
+ select tcl_spi_exec(true, 'error');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: error
+ ERROR: error message
+ select tcl_spi_exec(true, 'return');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: return
+ tcl_spi_exec
+ --------------
+
+ (1 row)
+
+ select tcl_spi_exec(false, 'break');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: break
+ NOTICE: end of function
+ tcl_spi_exec
+ --------------
+
+ (1 row)
+
+ select tcl_spi_exec(false, 'continue');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: continue
+ NOTICE: attnum 3, attname reltype
+ NOTICE: end of function
+ tcl_spi_exec
+ --------------
+
+ (1 row)
+
+ select tcl_spi_exec(false, 'error');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: error
+ ERROR: error message
+ select tcl_spi_exec(false, 'return');
+ NOTICE: attnum 1, attname relname
+ NOTICE: attnum 2, attname relnamespace
+ NOTICE: action: return
+ tcl_spi_exec
+ --------------
+
+ (1 row)
+
diff --cc src/pl/tcl/expected/pltcl_setup.out
index ed99d9b492,357155006a..0000000000
--- a/src/pl/tcl/expected/pltcl_setup.out
+++ b/src/pl/tcl/expected/pltcl_setup.out
@@@ -555,29 -579,28 +579,41 @@@ NOTICE: tclsnitch: ddl_command_start D
NOTICE: tclsnitch: ddl_command_end DROP TABLE
drop event trigger tcl_a_snitch;
drop event trigger tcl_b_snitch;
- CREATE FUNCTION tcl_test_cube_squared(in int, out squared int, out cubed int)
AS $$
-CREATE OR REPLACE FUNCTION tcl_eval(in string varchar) RETURNS text AS $$
++create function tcl_test_cube_squared(in int, out squared int, out cubed int)
as $$
+ return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
+$$ language pltcl;
- CREATE FUNCTION tcl_test_squared_rows(int,int) RETURNS TABLE (x int, y int)
AS $$
++create function tcl_test_squared_rows(int,int) returns table (x int, y int)
as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next [list y [expr {$i * $i}] x $i]
+ }
+$$ language pltcl;
- CREATE FUNCTION tcl_test_sequence(int,int) RETURNS SETOF int AS $$
++create function tcl_test_sequence(int,int) returns setof int as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next $i
+ }
+$$ language pltcl;
- -- test use of errorCode in error handling
- create function tcl_error_handling_test() returns text as $$
- global errorCode
- if {[catch { spi_exec "select no_such_column from foo;" }]} {
- array set errArray $errorCode
- if {$errArray(condition) == "undefined_table"} {
- return "expected error: $errArray(message)"
- } else {
- return "unexpected error: $errArray(condition) $errArray(message)"
- }
++create or replace function tcl_eval(in string varchar) returns text as $$
+ eval $1
-$$ LANGUAGE 'pltcl';
--- test use of errorCode in error handling
-CREATE FUNCTION public.tcl_error_handling_test(text)
- RETURNS text
- LANGUAGE pltcl
-AS $function$
++$$ language 'pltcl';
++-- test use of errorcode in error handling
++create function public.tcl_error_handling_test(text)
++ returns text
++ language pltcl
++as $function$
+ if {[catch $1 err]} {
+ # Set keys that will change over time to fixed values
+ array set myArray $::errorCode
+ set myArray(funcname) "'funcname'"
+ set myArray(lineno) 'lineno'
+ set myArray(POSTGRES) 'POSTGRES'
+
+ # Format into something nicer
+ set vals []
+ foreach {key} [lsort [array names myArray]] {
+ set value [string map {"\n" "\n\t"} $myArray($key)]
+ lappend vals "$key: $value"
+ }
+ return [join $vals "\n"]
} else {
return "no error"
}
diff --cc src/pl/tcl/sql/pltcl_queries.sql
index 0ebfe65340,5c584d808a..0000000000
--- a/src/pl/tcl/sql/pltcl_queries.sql
+++ b/src/pl/tcl/sql/pltcl_queries.sql
@@@ -91,6 -98,8 +98,9 @@@ select tcl_composite_arg_ref2(row('tkey
select tcl_argisnull('foo');
select tcl_argisnull('');
select tcl_argisnull(null);
+ -- should error
+ insert into trigger_test(test_argisnull) values(true);
++select trigger_data();
-- Test spi_lastoid primitive
create temp table t1 (f1 int);
@@@ -98,35 -107,75 +108,108 @@@ select tcl_lastoid('t1')
create temp table t2 (f1 int) with oids;
select tcl_lastoid('t2') > 0;
--- Test quote
+-- test some error cases
- CREATE FUNCTION tcl_error(OUT a int, OUT b int) AS $$return {$$ LANGUAGE
pltcl;
- SELECT tcl_error();
++create function tcl_error(out a int, out b int) as $$return {$$ language
pltcl;
++select tcl_error();
+
- CREATE FUNCTION bad_record(OUT a text, OUT b text) AS $$return [list a]$$
LANGUAGE pltcl;
- SELECT bad_record();
++create function bad_record(out a text, out b text) as $$return [list a]$$
language pltcl;
++select bad_record();
+
- CREATE FUNCTION bad_field(OUT a text, OUT b text) AS $$return [list a 1 b 2
cow 3]$$ LANGUAGE pltcl;
- SELECT bad_field();
++create function bad_field(out a text, out b text) as $$return [list a 1 b 2
cow 3]$$ language pltcl;
++select bad_field();
+
+-- test compound return
+select * from tcl_test_cube_squared(5);
+
- -- test SRF
++-- test srf
+select * from tcl_test_squared_rows(0,5);
+
+select * from tcl_test_sequence(0,5) as a;
+
+select 1, tcl_test_sequence(0,5);
+
- CREATE FUNCTION non_srf() RETURNS int AS $$return_next 1$$ LANGUAGE pltcl;
++create function non_srf() returns int as $$return_next 1$$ language pltcl;
+select non_srf();
+
- CREATE FUNCTION bad_record_srf(OUT a text, OUT b text) RETURNS SETOF record
AS $$
++create function bad_record_srf(out a text, out b text) returns setof record
as $$
+return_next [list a]
- $$ LANGUAGE pltcl;
- SELECT bad_record_srf();
++$$ language pltcl;
++select bad_record_srf();
+
- CREATE FUNCTION bad_field_srf(OUT a text, OUT b text) RETURNS SETOF record AS
$$
++create function bad_field_srf(out a text, out b text) returns setof record as
$$
+return_next [list a 1 b 2 cow 3]
- $$ LANGUAGE pltcl;
- SELECT bad_field_srf();
++$$ language pltcl;
++select bad_field_srf();
++
++-- test quote
+ select tcl_eval('quote foo bar');
+ select tcl_eval('quote [format %c 39]');
+ select tcl_eval('quote [format %c 92]');
+
+ -- Test argisnull
+ select tcl_eval('argisnull');
+ select tcl_eval('argisnull 14');
+ select tcl_eval('argisnull abc');
+
+ -- Test return_null
+ select tcl_eval('return_null 14');
+ -- should error
+ insert into trigger_test(test_return_null) values(true);
+
+ -- Test spi_exec
+ select tcl_eval('spi_exec');
+ select tcl_eval('spi_exec -count');
+ select tcl_eval('spi_exec -array');
+ select tcl_eval('spi_exec -count abc');
+ select tcl_eval('spi_exec query loop body toomuch');
+ select tcl_eval('spi_exec "begin; rollback;"');
+
+ -- Test spi_execp
+ select tcl_eval('spi_execp');
+ select tcl_eval('spi_execp -count');
+ select tcl_eval('spi_execp -array');
+ select tcl_eval('spi_execp -count abc');
+ select tcl_eval('spi_execp -nulls');
+ select tcl_eval('spi_execp ""');
+
+ -- test spi_prepare
+ select tcl_eval('spi_prepare');
+ select tcl_eval('spi_prepare a b');
+ select tcl_eval('spi_prepare a "b {"');
+ select tcl_error_handling_test($tcl${ spi_prepare "moo" }$tcl$);
+
+ -- test full error text
+ select tcl_error_handling_test($tcl$
+ spi_exec "DO $$
+ BEGIN
+ RAISE 'message'
+ USING HINT = 'hint'
+ , DETAIL = 'detail'
+ , SCHEMA = 'schema'
+ , TABLE = 'table'
+ , COLUMN = 'column'
+ , CONSTRAINT = 'constraint'
+ , DATATYPE = 'datatype'
+ ;
+ END$$;"
+ $tcl$);
+
+
+ -- test elog
+ select tcl_eval('elog');
+ select tcl_eval('elog foo bar');
+
+ -- test forced error
+ select tcl_eval('error "forced error"');
+
+ select tcl_eval('unset -nocomplain ::tcl_vwait; after 100 {set ::tcl_vwait
1}; vwait ::tcl_vwait; unset -nocomplain ::tcl_vwait');
+
+ -- test loop control
+ select tcl_spi_exec(true, 'break');
+ select tcl_spi_exec(true, 'continue');
+ select tcl_spi_exec(true, 'error');
+ select tcl_spi_exec(true, 'return');
+ select tcl_spi_exec(false, 'break');
+ select tcl_spi_exec(false, 'continue');
+ select tcl_spi_exec(false, 'error');
+ select tcl_spi_exec(false, 'return');
diff --cc src/pl/tcl/sql/pltcl_setup.sql
index 58f38d53aa,664f3dbe8a..0000000000
--- a/src/pl/tcl/sql/pltcl_setup.sql
+++ b/src/pl/tcl/sql/pltcl_setup.sql
@@@ -596,33 -620,31 +620,47 @@@ drop table foo
drop event trigger tcl_a_snitch;
drop event trigger tcl_b_snitch;
- CREATE FUNCTION tcl_test_cube_squared(in int, out squared int, out cubed int)
AS $$
-CREATE OR REPLACE FUNCTION tcl_eval(in string varchar) RETURNS text AS $$
++create function tcl_test_cube_squared(in int, out squared int, out cubed int)
as $$
+ return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
+$$ language pltcl;
+
- CREATE FUNCTION tcl_test_squared_rows(int,int) RETURNS TABLE (x int, y int)
AS $$
++create function tcl_test_squared_rows(int,int) returns table (x int, y int)
as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next [list y [expr {$i * $i}] x $i]
+ }
+$$ language pltcl;
+
- CREATE FUNCTION tcl_test_sequence(int,int) RETURNS SETOF int AS $$
++create function tcl_test_sequence(int,int) returns setof int as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next $i
+ }
+$$ language pltcl;
+
- -- test use of errorCode in error handling
-
- create function tcl_error_handling_test() returns text as $$
- global errorCode
- if {[catch { spi_exec "select no_such_column from foo;" }]} {
- array set errArray $errorCode
- if {$errArray(condition) == "undefined_table"} {
- return "expected error: $errArray(message)"
- } else {
- return "unexpected error: $errArray(condition) $errArray(message)"
- }
++create or replace function tcl_eval(in string varchar) returns text as $$
+ eval $1
-$$ LANGUAGE 'pltcl';
++$$ language 'pltcl';
+
+
--- test use of errorCode in error handling
++-- test use of errorcode in error handling
+
-CREATE FUNCTION public.tcl_error_handling_test(text)
- RETURNS text
- LANGUAGE pltcl
-AS $function$
++create function public.tcl_error_handling_test(text)
++ returns text
++ language pltcl
++as $function$
+ if {[catch $1 err]} {
+ # Set keys that will change over time to fixed values
+ array set myArray $::errorCode
+ set myArray(funcname) "'funcname'"
+ set myArray(lineno) 'lineno'
+ set myArray(POSTGRES) 'POSTGRES'
+
+ # Format into something nicer
+ set vals []
+ foreach {key} [lsort [array names myArray]] {
+ set value [string map {"\n" "\n\t"} $myArray($key)]
+ lappend vals "$key: $value"
+ }
+ return [join $vals "\n"]
} else {
return "no error"
}
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers