Commit 961bed02 authored by Tom Lane's avatar Tom Lane

Expand the regression tests for PL/Tcl.

This raises the test coverage (by line count) in pltcl.c from about 70%
to 86%.

Karl Lehenbauer and Jim Nasby

Discussion: https://postgr.es/m/92a1670d-21b6-8f03-9c13-e4fb2207ab7b@BlueTreble.com
parent 534b6f3e
...@@ -185,12 +185,23 @@ select * from T_pkey2 order by key1 using @<, key2 collate "C"; ...@@ -185,12 +185,23 @@ select * from T_pkey2 order by key1 using @<, key2 collate "C";
-- show dump of trigger data -- show dump of trigger data
insert into trigger_test values(1,'insert'); insert into trigger_test values(1,'insert');
NOTICE: NEW: {i: 1, v: insert} 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}}
NOTICE: NEW: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: insert}
NOTICE: OLD: {} NOTICE: OLD: {}
NOTICE: TG_level: ROW NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig NOTICE: TG_name: show_trigger_data_trig
NOTICE: TG_op: INSERT NOTICE: TG_op: INSERT
NOTICE: TG_relatts: {{} i v} NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
NOTICE: TG_relid: bogus:12345 NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public NOTICE: TG_table_schema: public
...@@ -232,13 +243,37 @@ NOTICE: TG_table_name: trigger_test_view ...@@ -232,13 +243,37 @@ NOTICE: TG_table_name: trigger_test_view
NOTICE: TG_table_schema: public NOTICE: TG_table_schema: public
NOTICE: TG_when: {INSTEAD OF} NOTICE: TG_when: {INSTEAD OF}
NOTICE: args: {24 {skidoo view}} NOTICE: args: {24 {skidoo view}}
update trigger_test set v = 'update', test_skip=true where i = 1;
NOTICE: NEW: {}
NOTICE: OLD: {}
NOTICE: TG_level: STATEMENT
NOTICE: TG_name: statement_trigger
NOTICE: TG_op: UPDATE
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}}
NOTICE: SKIPPING OPERATION UPDATE
update trigger_test set v = 'update' where i = 1; update trigger_test set v = 'update' where i = 1;
NOTICE: NEW: {i: 1, v: update} NOTICE: NEW: {}
NOTICE: OLD: {i: 1, v: insert} NOTICE: OLD: {}
NOTICE: TG_level: STATEMENT
NOTICE: TG_name: statement_trigger
NOTICE: TG_op: UPDATE
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}}
NOTICE: NEW: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: update}
NOTICE: OLD: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: insert}
NOTICE: TG_level: ROW NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig NOTICE: TG_name: show_trigger_data_trig
NOTICE: TG_op: UPDATE NOTICE: TG_op: UPDATE
NOTICE: TG_relatts: {{} i v} NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
NOTICE: TG_relid: bogus:12345 NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public NOTICE: TG_table_schema: public
...@@ -246,16 +281,39 @@ NOTICE: TG_when: BEFORE ...@@ -246,16 +281,39 @@ NOTICE: TG_when: BEFORE
NOTICE: args: {23 skidoo} NOTICE: args: {23 skidoo}
delete from trigger_test; delete from trigger_test;
NOTICE: NEW: {} NOTICE: NEW: {}
NOTICE: OLD: {i: 1, v: update} NOTICE: OLD: {}
NOTICE: TG_level: STATEMENT
NOTICE: TG_name: statement_trigger
NOTICE: TG_op: DELETE
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}}
NOTICE: NEW: {}
NOTICE: OLD: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: update}
NOTICE: TG_level: ROW NOTICE: TG_level: ROW
NOTICE: TG_name: show_trigger_data_trig NOTICE: TG_name: show_trigger_data_trig
NOTICE: TG_op: DELETE NOTICE: TG_op: DELETE
NOTICE: TG_relatts: {{} i v} NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
NOTICE: TG_relid: bogus:12345 NOTICE: TG_relid: bogus:12345
NOTICE: TG_table_name: trigger_test NOTICE: TG_table_name: trigger_test
NOTICE: TG_table_schema: public NOTICE: TG_table_schema: public
NOTICE: TG_when: BEFORE NOTICE: TG_when: BEFORE
NOTICE: args: {23 skidoo} NOTICE: args: {23 skidoo}
truncate trigger_test;
NOTICE: NEW: {}
NOTICE: OLD: {}
NOTICE: TG_level: STATEMENT
NOTICE: TG_name: statement_trigger
NOTICE: TG_op: TRUNCATE
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}}
-- Test composite-type arguments -- Test composite-type arguments
select tcl_composite_arg_ref1(row('tkey', 42, 'ref2')); select tcl_composite_arg_ref1(row('tkey', 42, 'ref2'));
tcl_composite_arg_ref1 tcl_composite_arg_ref1
...@@ -288,6 +346,22 @@ select tcl_argisnull(null); ...@@ -288,6 +346,22 @@ select tcl_argisnull(null);
t t
(1 row) (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 -- Test spi_lastoid primitive
create temp table t1 (f1 int); create temp table t1 (f1 int);
select tcl_lastoid('t1'); select tcl_lastoid('t1');
...@@ -304,14 +378,14 @@ select tcl_lastoid('t2') > 0; ...@@ -304,14 +378,14 @@ select tcl_lastoid('t2') > 0;
(1 row) (1 row)
-- test some error cases -- test some error cases
CREATE FUNCTION tcl_error(OUT a int, OUT b int) AS $$return {$$ LANGUAGE pltcl; create function tcl_error(out a int, out b int) as $$return {$$ language pltcl;
SELECT tcl_error(); select tcl_error();
ERROR: missing close-brace ERROR: missing close-brace
CREATE FUNCTION bad_record(OUT a text, OUT b text) AS $$return [list a]$$ LANGUAGE pltcl; create function bad_record(out a text, out b text) as $$return [list a]$$ language pltcl;
SELECT bad_record(); select bad_record();
ERROR: column name/value list must have even number of elements 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; create function bad_field(out a text, out b text) as $$return [list a 1 b 2 cow 3]$$ language pltcl;
SELECT bad_field(); select bad_field();
ERROR: column name/value list contains nonexistent column name "cow" ERROR: column name/value list contains nonexistent column name "cow"
-- test compound return -- test compound return
select * from tcl_test_cube_squared(5); select * from tcl_test_cube_squared(5);
...@@ -351,16 +425,238 @@ select 1, tcl_test_sequence(0,5); ...@@ -351,16 +425,238 @@ select 1, tcl_test_sequence(0,5);
1 | 4 1 | 4
(5 rows) (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(); select non_srf();
ERROR: return_next cannot be used in non-set-returning functions 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] return_next [list a]
$$ LANGUAGE pltcl; $$ language pltcl;
SELECT bad_record_srf(); select bad_record_srf();
ERROR: column name/value list must have even number of elements 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] return_next [list a 1 b 2 cow 3]
$$ LANGUAGE pltcl; $$ language pltcl;
SELECT bad_field_srf(); select bad_field_srf();
ERROR: column name/value list contains nonexistent column name "cow" 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: 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: 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 "select moo" []$tcl$);
tcl_error_handling_test
--------------------------------------
SQLSTATE: 42703 +
condition: undefined_column +
cursor_position: 8 +
message: column "moo" does not exist+
statement: select moo
(1 row)
-- test full error text
select tcl_error_handling_test($tcl$
spi_exec "DO $$
BEGIN
RAISE 'my message'
USING HINT = 'my hint'
, DETAIL = 'my detail'
, SCHEMA = 'my schema'
, TABLE = 'my table'
, COLUMN = 'my column'
, CONSTRAINT = 'my constraint'
, DATATYPE = 'my datatype'
;
END$$;"
$tcl$);
tcl_error_handling_test
--------------------------------------------------------------
SQLSTATE: P0001 +
column: my column +
condition: raise_exception +
constraint: my constraint +
context: PL/pgSQL function inline_code_block line 3 at RAISE+
SQL statement "DO $$ +
BEGIN +
RAISE 'my message' +
USING HINT = 'my hint' +
, DETAIL = 'my detail' +
, SCHEMA = 'my schema' +
, TABLE = 'my table' +
, COLUMN = 'my column' +
, CONSTRAINT = 'my constraint' +
, DATATYPE = 'my datatype' +
; +
END$$;" +
datatype: my datatype +
detail: my detail +
hint: my hint +
message: my message +
schema: my schema +
table: my table
(1 row)
-- verify tcl_error_handling_test() properly reports non-postgres errors
select tcl_error_handling_test('moo');
tcl_error_handling_test
----------------------------
invalid command name "moo"
(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
-- test loop control in spi_exec[p]
select tcl_spi_exec(true, 'break');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: break
NOTICE: end of function
tcl_spi_exec
--------------
(1 row)
select tcl_spi_exec(true, 'continue');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: continue
NOTICE: col1 3, col2 baz
NOTICE: end of function
tcl_spi_exec
--------------
(1 row)
select tcl_spi_exec(true, 'error');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: error
ERROR: error message
select tcl_spi_exec(true, 'return');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: return
tcl_spi_exec
--------------
(1 row)
select tcl_spi_exec(false, 'break');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: break
NOTICE: end of function
tcl_spi_exec
--------------
(1 row)
select tcl_spi_exec(false, 'continue');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: continue
NOTICE: col1 3, col2 baz
NOTICE: end of function
tcl_spi_exec
--------------
(1 row)
select tcl_spi_exec(false, 'error');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: error
ERROR: error message
select tcl_spi_exec(false, 'return');
NOTICE: col1 1, col2 foo
NOTICE: col1 2, col2 bar
NOTICE: action: return
tcl_spi_exec
--------------
(1 row)
-- forcibly run the Tcl event loop for awhile, to check that we have not
-- messed things up too badly by disabling the Tcl notifier subsystem
select tcl_eval($$
unset -nocomplain ::tcl_vwait
after 100 {set ::tcl_vwait 1}
vwait ::tcl_vwait
unset -nocomplain ::tcl_vwait$$);
tcl_eval
----------
(1 row)
...@@ -49,10 +49,31 @@ create function check_pkey1_exists(int4, bpchar) returns bool as E' ...@@ -49,10 +49,31 @@ create function check_pkey1_exists(int4, bpchar) returns bool as E'
return "f" return "f"
' language pltcl; ' language pltcl;
-- dump trigger data -- dump trigger data
CREATE TABLE trigger_test CREATE TABLE trigger_test (
(i int, v text ); i int,
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; v text,
dropme text,
test_skip boolean DEFAULT false,
test_return_null boolean DEFAULT false,
test_argisnull boolean DEFAULT false
);
-- Make certain dropped attributes are handled correctly
ALTER TABLE trigger_test DROP dropme;
CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$ CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} {
# Special case tests
if {$NEW(test_return_null) eq "t" } {
return_null
}
if {$NEW(test_argisnull) eq "t" } {
set should_error [argisnull 1]
}
if {$NEW(test_skip) eq "t" } {
elog NOTICE "SKIPPING OPERATION $TG_op"
return SKIP
}
}
if { [info exists TG_relid] } { if { [info exists TG_relid] } {
set TG_relid "bogus:12345" set TG_relid "bogus:12345"
...@@ -86,6 +107,9 @@ $_$; ...@@ -86,6 +107,9 @@ $_$;
CREATE TRIGGER show_trigger_data_trig CREATE TRIGGER show_trigger_data_trig
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER statement_trigger
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');
CREATE TRIGGER show_trigger_data_view_trig CREATE TRIGGER show_trigger_data_view_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
...@@ -533,12 +557,12 @@ select tcl_date_week(2001,10,24); ...@@ -533,12 +557,12 @@ select tcl_date_week(2001,10,24);
(1 row) (1 row)
-- test pltcl event triggers -- test pltcl event triggers
create or replace function tclsnitch() returns event_trigger language pltcl as $$ create function tclsnitch() returns event_trigger language pltcl as $$
elog NOTICE "tclsnitch: $TG_event $TG_tag" elog NOTICE "tclsnitch: $TG_event $TG_tag"
$$; $$;
create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch(); create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch(); create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
create or replace function foobar() returns int language sql as $$select 1;$$; create function foobar() returns int language sql as $$select 1;$$;
NOTICE: tclsnitch: ddl_command_start CREATE FUNCTION NOTICE: tclsnitch: ddl_command_start CREATE FUNCTION
NOTICE: tclsnitch: ddl_command_end CREATE FUNCTION NOTICE: tclsnitch: ddl_command_end CREATE FUNCTION
alter function foobar() cost 77; alter function foobar() cost 77;
...@@ -555,44 +579,108 @@ NOTICE: tclsnitch: ddl_command_start DROP TABLE ...@@ -555,44 +579,108 @@ NOTICE: tclsnitch: ddl_command_start DROP TABLE
NOTICE: tclsnitch: ddl_command_end DROP TABLE NOTICE: tclsnitch: ddl_command_end DROP TABLE
drop event trigger tcl_a_snitch; drop event trigger tcl_a_snitch;
drop event trigger tcl_b_snitch; drop event trigger tcl_b_snitch;
CREATE FUNCTION tcl_test_cube_squared(in int, out squared int, out cubed int) 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}]] return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
$$ language pltcl; $$ 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} { for {set i $1} {$i < $2} {incr i} {
return_next [list y [expr {$i * $i}] x $i] return_next [list y [expr {$i * $i}] x $i]
} }
$$ language pltcl; $$ 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} { for {set i $1} {$i < $2} {incr i} {
return_next $i return_next $i
} }
$$ language pltcl; $$ language pltcl;
create function tcl_eval(string text) returns text as $$
eval $1
$$ language pltcl;
-- test use of errorCode in error handling -- test use of errorCode in error handling
create function tcl_error_handling_test() returns text as $$ create function tcl_error_handling_test(text) returns text
global errorCode language pltcl
if {[catch { spi_exec "select no_such_column from foo;" }]} { as $function$
array set errArray $errorCode if {[catch $1 err]} {
if {$errArray(condition) == "undefined_table"} { # If not a Postgres error, just return the basic error message
return "expected error: $errArray(message)" if {[lindex $::errorCode 0] != "POSTGRES"} {
} else { return $err
return "unexpected error: $errArray(condition) $errArray(message)" }
}
# Get rid of keys that can't be expected to remain constant
array set myArray $::errorCode
unset myArray(POSTGRES)
unset myArray(funcname)
unset myArray(filename)
unset myArray(lineno)
# 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 { } else {
return "no error" return "no error"
} }
$$ language pltcl; $function$;
select tcl_error_handling_test(); -- test spi_exec and spi_execp with -array
tcl_error_handling_test create function tcl_spi_exec(
----------------------------------------------- prepare boolean,
expected error: relation "foo" does not exist action text
(1 row) )
returns void language pltcl AS $function$
set query "select * from (values (1,'foo'),(2,'bar'),(3,'baz')) v(col1,col2)"
if {$1 == "t"} {
set prep [spi_prepare $query {}]
spi_execp -array A $prep {
elog NOTICE "col1 $A(col1), col2 $A(col2)"
create temp table foo(f1 int); switch $A(col1) {
select tcl_error_handling_test(); 2 {
tcl_error_handling_test elog NOTICE "action: $2"
--------------------------------------------------------------------------- switch $2 {
unexpected error: undefined_column column "no_such_column" does not exist break {
(1 row) break
}
continue {
continue
}
return {
return
}
error {
error "error message"
}
}
error "should not get here"
}
}
}
} else {
spi_exec -array A $query {
elog NOTICE "col1 $A(col1), col2 $A(col2)"
drop table foo; switch $A(col1) {
2 {
elog NOTICE "action: $2"
switch $2 {
break {
break
}
continue {
continue
}
return {
return
}
error {
error "error message"
}
}
error "should not get here"
}
}
}
}
elog NOTICE "end of function"
$function$;
...@@ -80,8 +80,10 @@ insert into trigger_test_view values(2,'insert'); ...@@ -80,8 +80,10 @@ insert into trigger_test_view values(2,'insert');
update trigger_test_view set v = 'update' where i=1; update trigger_test_view set v = 'update' where i=1;
delete from trigger_test_view; delete from trigger_test_view;
update trigger_test set v = 'update', test_skip=true where i = 1;
update trigger_test set v = 'update' where i = 1; update trigger_test set v = 'update' where i = 1;
delete from trigger_test; delete from trigger_test;
truncate trigger_test;
-- Test composite-type arguments -- Test composite-type arguments
select tcl_composite_arg_ref1(row('tkey', 42, 'ref2')); select tcl_composite_arg_ref1(row('tkey', 42, 'ref2'));
...@@ -91,6 +93,9 @@ select tcl_composite_arg_ref2(row('tkey', 42, 'ref2')); ...@@ -91,6 +93,9 @@ select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
select tcl_argisnull('foo'); select tcl_argisnull('foo');
select tcl_argisnull(''); select tcl_argisnull('');
select tcl_argisnull(null); select tcl_argisnull(null);
-- should error
insert into trigger_test(test_argisnull) values(true);
select trigger_data();
-- Test spi_lastoid primitive -- Test spi_lastoid primitive
create temp table t1 (f1 int); create temp table t1 (f1 int);
...@@ -99,14 +104,14 @@ create temp table t2 (f1 int) with oids; ...@@ -99,14 +104,14 @@ create temp table t2 (f1 int) with oids;
select tcl_lastoid('t2') > 0; select tcl_lastoid('t2') > 0;
-- test some error cases -- test some error cases
CREATE FUNCTION tcl_error(OUT a int, OUT b int) AS $$return {$$ LANGUAGE pltcl; create function tcl_error(out a int, out b int) as $$return {$$ language pltcl;
SELECT tcl_error(); select tcl_error();
CREATE FUNCTION bad_record(OUT a text, OUT b text) AS $$return [list a]$$ LANGUAGE pltcl; create function bad_record(out a text, out b text) as $$return [list a]$$ language pltcl;
SELECT bad_record(); select bad_record();
CREATE FUNCTION bad_field(OUT a text, OUT b text) AS $$return [list a 1 b 2 cow 3]$$ LANGUAGE pltcl; create function bad_field(out a text, out b text) as $$return [list a 1 b 2 cow 3]$$ language pltcl;
SELECT bad_field(); select bad_field();
-- test compound return -- test compound return
select * from tcl_test_cube_squared(5); select * from tcl_test_cube_squared(5);
...@@ -118,15 +123,96 @@ select * from tcl_test_sequence(0,5) as a; ...@@ -118,15 +123,96 @@ select * from tcl_test_sequence(0,5) as a;
select 1, tcl_test_sequence(0,5); 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(); 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] return_next [list a]
$$ LANGUAGE pltcl; $$ language pltcl;
SELECT bad_record_srf(); 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] return_next [list a 1 b 2 cow 3]
$$ LANGUAGE pltcl; $$ language pltcl;
SELECT bad_field_srf(); 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 "select moo" []$tcl$);
-- test full error text
select tcl_error_handling_test($tcl$
spi_exec "DO $$
BEGIN
RAISE 'my message'
USING HINT = 'my hint'
, DETAIL = 'my detail'
, SCHEMA = 'my schema'
, TABLE = 'my table'
, COLUMN = 'my column'
, CONSTRAINT = 'my constraint'
, DATATYPE = 'my datatype'
;
END$$;"
$tcl$);
-- verify tcl_error_handling_test() properly reports non-postgres errors
select tcl_error_handling_test('moo');
-- test elog
select tcl_eval('elog');
select tcl_eval('elog foo bar');
-- test forced error
select tcl_eval('error "forced error"');
-- test loop control in spi_exec[p]
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');
-- forcibly run the Tcl event loop for awhile, to check that we have not
-- messed things up too badly by disabling the Tcl notifier subsystem
select tcl_eval($$
unset -nocomplain ::tcl_vwait
after 100 {set ::tcl_vwait 1}
vwait ::tcl_vwait
unset -nocomplain ::tcl_vwait$$);
...@@ -57,12 +57,33 @@ create function check_pkey1_exists(int4, bpchar) returns bool as E' ...@@ -57,12 +57,33 @@ create function check_pkey1_exists(int4, bpchar) returns bool as E'
-- dump trigger data -- dump trigger data
CREATE TABLE trigger_test CREATE TABLE trigger_test (
(i int, v text ); i int,
v text,
dropme text,
test_skip boolean DEFAULT false,
test_return_null boolean DEFAULT false,
test_argisnull boolean DEFAULT false
);
-- Make certain dropped attributes are handled correctly
ALTER TABLE trigger_test DROP dropme;
CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$ CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} {
# Special case tests
if {$NEW(test_return_null) eq "t" } {
return_null
}
if {$NEW(test_argisnull) eq "t" } {
set should_error [argisnull 1]
}
if {$NEW(test_skip) eq "t" } {
elog NOTICE "SKIPPING OPERATION $TG_op"
return SKIP
}
}
if { [info exists TG_relid] } { if { [info exists TG_relid] } {
set TG_relid "bogus:12345" set TG_relid "bogus:12345"
...@@ -97,6 +118,9 @@ $_$; ...@@ -97,6 +118,9 @@ $_$;
CREATE TRIGGER show_trigger_data_trig CREATE TRIGGER show_trigger_data_trig
BEFORE INSERT OR UPDATE OR DELETE ON trigger_test BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
CREATE TRIGGER statement_trigger
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');
CREATE TRIGGER show_trigger_data_view_trig CREATE TRIGGER show_trigger_data_view_trig
INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
...@@ -579,14 +603,14 @@ select tcl_date_week(2010,1,24); ...@@ -579,14 +603,14 @@ select tcl_date_week(2010,1,24);
select tcl_date_week(2001,10,24); select tcl_date_week(2001,10,24);
-- test pltcl event triggers -- test pltcl event triggers
create or replace function tclsnitch() returns event_trigger language pltcl as $$ create function tclsnitch() returns event_trigger language pltcl as $$
elog NOTICE "tclsnitch: $TG_event $TG_tag" elog NOTICE "tclsnitch: $TG_event $TG_tag"
$$; $$;
create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch(); create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch(); create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
create or replace function foobar() returns int language sql as $$select 1;$$; create function foobar() returns int language sql as $$select 1;$$;
alter function foobar() cost 77; alter function foobar() cost 77;
drop function foobar(); drop function foobar();
...@@ -596,42 +620,113 @@ drop table foo; ...@@ -596,42 +620,113 @@ drop table foo;
drop event trigger tcl_a_snitch; drop event trigger tcl_a_snitch;
drop event trigger tcl_b_snitch; drop event trigger tcl_b_snitch;
CREATE FUNCTION tcl_test_cube_squared(in int, out squared int, out cubed int) 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}]] return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
$$ language pltcl; $$ 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} { for {set i $1} {$i < $2} {incr i} {
return_next [list y [expr {$i * $i}] x $i] return_next [list y [expr {$i * $i}] x $i]
} }
$$ language pltcl; $$ 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} { for {set i $1} {$i < $2} {incr i} {
return_next $i return_next $i
} }
$$ language pltcl; $$ language pltcl;
-- test use of errorCode in error handling create function tcl_eval(string text) returns text as $$
eval $1
$$ language pltcl;
create function tcl_error_handling_test() returns text as $$ -- test use of errorCode in error handling
global errorCode create function tcl_error_handling_test(text) returns text
if {[catch { spi_exec "select no_such_column from foo;" }]} { language pltcl
array set errArray $errorCode as $function$
if {$errArray(condition) == "undefined_table"} { if {[catch $1 err]} {
return "expected error: $errArray(message)" # If not a Postgres error, just return the basic error message
} else { if {[lindex $::errorCode 0] != "POSTGRES"} {
return "unexpected error: $errArray(condition) $errArray(message)" return $err
} }
# Get rid of keys that can't be expected to remain constant
array set myArray $::errorCode
unset myArray(POSTGRES)
unset myArray(funcname)
unset myArray(filename)
unset myArray(lineno)
# 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 { } else {
return "no error" return "no error"
} }
$$ language pltcl; $function$;
select tcl_error_handling_test();
create temp table foo(f1 int); -- test spi_exec and spi_execp with -array
create function tcl_spi_exec(
prepare boolean,
action text
)
returns void language pltcl AS $function$
set query "select * from (values (1,'foo'),(2,'bar'),(3,'baz')) v(col1,col2)"
if {$1 == "t"} {
set prep [spi_prepare $query {}]
spi_execp -array A $prep {
elog NOTICE "col1 $A(col1), col2 $A(col2)"
select tcl_error_handling_test(); switch $A(col1) {
2 {
elog NOTICE "action: $2"
switch $2 {
break {
break
}
continue {
continue
}
return {
return
}
error {
error "error message"
}
}
error "should not get here"
}
}
}
} else {
spi_exec -array A $query {
elog NOTICE "col1 $A(col1), col2 $A(col2)"
drop table foo; switch $A(col1) {
2 {
elog NOTICE "action: $2"
switch $2 {
break {
break
}
continue {
continue
}
return {
return
}
error {
error "error message"
}
}
error "should not get here"
}
}
}
}
elog NOTICE "end of function"
$function$;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment