diff options
author | Pallavi Sontakke | 2015-09-07 08:26:36 +0000 |
---|---|---|
committer | Pallavi Sontakke | 2015-09-07 08:26:36 +0000 |
commit | 215fa6f2b1036368e8ec007e40f01835c821a729 (patch) | |
tree | 7db670a82ba8985d78bf6781dfa001d87bde06cd | |
parent | 5d0972e357b21890b402f6737760f10d22ceff7e (diff) |
Some more tests on UDF like: reads from just 1 node, reads from multiple nodes, writes to just node, writes to multiple nodes, writes to one node and then read again, do a join which requires data from one node, multiple nodes, use ddl etc
-rwxr-xr-x[-rw-r--r--] | src/test/regress/expected/xl_user_defined_functions.out | 393 | ||||
-rwxr-xr-x | src/test/regress/sql/xl_user_defined_functions.sql | 271 |
2 files changed, 663 insertions, 1 deletions
diff --git a/src/test/regress/expected/xl_user_defined_functions.out b/src/test/regress/expected/xl_user_defined_functions.out index 5852116593..48af42c57a 100644..100755 --- a/src/test/regress/expected/xl_user_defined_functions.out +++ b/src/test/regress/expected/xl_user_defined_functions.out @@ -225,8 +225,401 @@ select xl_nodename_from_id1(xc_node_id), * from xl_Pline1 order by slotname; datanode_1 | PL.029 | -502 | Fax first floor | PS.first.ta1 (29 rows) +-- other user-defined-functions: +-- +-- Test the FOUND magic variable +-- +-- table distributed by hash on a by default +CREATE TABLE xl_found_test_tbl (a int, b int) ; +create function xl_test_found() + returns boolean as ' + declare + begin + insert into xl_found_test_tbl values (1, 1); + if FOUND then + insert into xl_found_test_tbl values (2, 2); + end if; + + update xl_found_test_tbl set b = 100 where a = 1; + if FOUND then + insert into xl_found_test_tbl values (3, 3); + end if; + + delete from xl_found_test_tbl where a = 9999; -- matches no rows + if not FOUND then + insert into xl_found_test_tbl values (4, 4); + end if; + + for i in 1 .. 10 loop + -- no need to do anything + end loop; + if FOUND then + insert into xl_found_test_tbl values (5, 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 xl_found_test_tbl values (6, 6); + end if; + return true; + end;' language plpgsql; +select xl_test_found(); + xl_test_found +--------------- + t +(1 row) + +select * from xl_found_test_tbl order by 1; + a | b +---+----- + 1 | 100 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 +(6 rows) + +-- +-- Test set-returning functions for PL/pgSQL +-- +create function xl_test_table_func_rec() returns setof xl_found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from xl_found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language plpgsql; +select * from xl_test_table_func_rec() order by 1; + a | b +---+----- + 1 | 100 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 +(6 rows) + +-- reads from just 1 node, reads from multiple nodes, writes to just node, writes to multiple nodes, writes to one node and then read again, do a join which requires data from one node, multiple nodes, use ddl etc +--reads from multiple nodes +create function xl_test_table_func_row() returns setof xl_found_test_tbl as ' +DECLARE + row xl_found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from xl_found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; +select * from xl_test_table_func_row() order by 1; + a | b +---+----- + 1 | 100 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 +(6 rows) + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 2 | 2 + datanode_1 | 1 | 100 + datanode_1 | 5 | 5 + datanode_1 | 6 | 6 + datanode_2 | 3 | 3 + datanode_2 | 4 | 4 +(6 rows) + +--reads from just 1 node +create function xl_read_from_one_node(name) returns setof xl_found_test_tbl as ' +DECLARE + row xl_found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from xl_found_test_tbl where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; +select xl_read_from_one_node('datanode_1'); + xl_read_from_one_node +----------------------- + (2,2) + (1,100) + (5,5) + (6,6) +(4 rows) + +select xl_read_from_one_node('datanode_2'); + xl_read_from_one_node +----------------------- + (3,3) + (4,4) +(2 rows) + +update xl_found_test_tbl set b = a;--re-set + +--writes to just node +create function xl_write_to_one_node(name) returns void as ' +BEGIN + update xl_found_test_tbl set b = b * 100 where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1); + RETURN; +END;' language plpgsql; +select xl_write_to_one_node('datanode_1'); + xl_write_to_one_node +---------------------- + +(1 row) + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 2 | 200 + datanode_1 | 1 | 100 + datanode_1 | 5 | 500 + datanode_1 | 6 | 600 + datanode_2 | 3 | 3 + datanode_2 | 4 | 4 +(6 rows) + +update xl_found_test_tbl set b = a;--re-set +--writes to multiple nodes +create function xl_write_to_multiple_nodes() returns void as ' +BEGIN + update xl_found_test_tbl set b = b * 100; + RETURN; +END;' language plpgsql; +select xl_write_to_multiple_nodes(); + xl_write_to_multiple_nodes +---------------------------- + +(1 row) + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 2 | 200 + datanode_1 | 1 | 100 + datanode_1 | 5 | 500 + datanode_1 | 6 | 600 + datanode_2 | 3 | 300 + datanode_2 | 4 | 400 +(6 rows) + +update xl_found_test_tbl set b = a;--re-set +--writes to one node and then read again, +create function xl_write_read_from_one_node(name) returns setof xl_found_test_tbl as ' +DECLARE + row xl_found_test_tbl%ROWTYPE; +BEGIN + update xl_found_test_tbl set b = b * 100 where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1); + FOR row IN select * from xl_found_test_tbl where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; +select xl_write_read_from_one_node('datanode_1'); + xl_write_read_from_one_node +----------------------------- + (2,200) + (1,100) + (5,500) + (6,600) +(4 rows) + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 2 | 200 + datanode_1 | 1 | 100 + datanode_1 | 5 | 500 + datanode_1 | 6 | 600 + datanode_2 | 3 | 3 + datanode_2 | 4 | 4 +(6 rows) + +update xl_found_test_tbl set b = a;--re-set +-- do a join which requires data from one node, +-- table is replicated on both nodes. +CREATE TABLE xl_join1_tbl (c int, d int) distribute by replication; +insert into xl_join1_tbl values (1, 100); +insert into xl_join1_tbl values (2, 200); +insert into xl_join1_tbl values (3, 300); +insert into xl_join1_tbl values (4, 400); +insert into xl_join1_tbl values (5, 500); +insert into xl_join1_tbl values (6, 600); +create function xl_join_using_1node(name) returns void as ' +BEGIN + update xl_found_test_tbl set b = xl_join1_tbl.d from xl_join1_tbl + where xl_join1_tbl.c in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) + and xl_found_test_tbl.a = xl_join1_tbl.c + and xl_join1_tbl.c <= 3; + RETURN; +END;' language plpgsql; +select xl_join_using_1node('datanode_1'); + xl_join_using_1node +--------------------- + +(1 row) + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 5 | 5 + datanode_1 | 6 | 6 + datanode_1 | 2 | 200 + datanode_1 | 1 | 100 + datanode_2 | 3 | 3 + datanode_2 | 4 | 4 +(6 rows) + +update xl_found_test_tbl set b = a;--re-set +-- do a join which requires data from multiple nodes +-- table distributed by hash(c) by default +CREATE TABLE xl_join2_tbl (c int, d int); +insert into xl_join2_tbl values (1, 100); +insert into xl_join2_tbl values (2, 200); +insert into xl_join2_tbl values (3, 300); +insert into xl_join2_tbl values (4, 400); +insert into xl_join2_tbl values (5, 500); +insert into xl_join2_tbl values (6, 600); +create function xl_join_using_more_nodes(name) returns void as ' +BEGIN + update xl_found_test_tbl set b = xl_join2_tbl.d from xl_join2_tbl + where xl_join2_tbl.c in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) + and xl_found_test_tbl.a = xl_join2_tbl.c + and xl_join2_tbl.c >= 3; + RETURN; +END;' language plpgsql; +select xl_join_using_more_nodes('datanode_1'); + xl_join_using_more_nodes +-------------------------- + +(1 row) + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 2 | 2 + datanode_1 | 1 | 1 + datanode_1 | 5 | 500 + datanode_1 | 6 | 600 + datanode_2 | 3 | 3 + datanode_2 | 4 | 4 +(6 rows) + +update xl_found_test_tbl set b = a;--re-set +-- use ddl etc +--DDL Commands - Create - Drop - Alter - Rename - Truncate +-- +create function xl_ddl_commands(name) returns void as ' +BEGIN + /* table distributed by hash on a by default*/ + CREATE TABLE xl_ddl_tbl1 (a int, b int) ; + insert into xl_ddl_tbl1 values (1,1); + insert into xl_ddl_tbl1 values (2,2); + insert into xl_ddl_tbl1 values (3,3); + insert into xl_ddl_tbl1 values (4,4); + insert into xl_ddl_tbl1 values (5,5); + insert into xl_ddl_tbl1 values (6,6); + + drop table xl_join2_tbl; + + /*table distributed by hash(c) by default*/ + CREATE TABLE xl_join3_tbl (c int); + + insert into xl_join3_tbl values (1); + insert into xl_join3_tbl values (2); + insert into xl_join3_tbl values (3); + insert into xl_join3_tbl values (4); + insert into xl_join3_tbl values (5); + insert into xl_join3_tbl values (6); + + alter table xl_join3_tbl add column d int; + + update xl_join3_tbl set d = c * 100; + + alter table xl_ddl_tbl1 rename to xl_ddl_tbl2; + + alter table xl_join3_tbl rename to xl_join4_tbl; + + truncate table xl_join1_tbl; + + update xl_ddl_tbl2 set b = xl_join4_tbl.d from xl_join4_tbl + where xl_join4_tbl.c in (select a from xl_ddl_tbl2 where xl_nodename_from_id1(xc_node_id) = $1) + and xl_ddl_tbl2.a = xl_join4_tbl.c + and xl_join4_tbl.c >= 3; + + RETURN; +END;' language plpgsql; +select xl_ddl_commands('datanode_1'); + xl_ddl_commands +----------------- + +(1 row) + +select xl_nodename_from_id1(xc_node_id), * from xl_join1_tbl; --truncated + xl_nodename_from_id1 | c | d +----------------------+---+--- +(0 rows) + +select xl_nodename_from_id1(xc_node_id), * from xl_join2_tbl; -- dropped +ERROR: relation "xl_join2_tbl" does not exist +LINE 1: select xl_nodename_from_id1(xc_node_id), * from xl_join2_tbl... + ^ +select xl_nodename_from_id1(xc_node_id), * from xl_join3_tbl; -- renamed to xl_join4_tbl +ERROR: relation "xl_join3_tbl" does not exist +LINE 1: select xl_nodename_from_id1(xc_node_id), * from xl_join3_tbl... + ^ +select xl_nodename_from_id1(xc_node_id), * from xl_join4_tbl; + xl_nodename_from_id1 | c | d +----------------------+---+----- + datanode_1 | 1 | 100 + datanode_1 | 2 | 200 + datanode_1 | 5 | 500 + datanode_1 | 6 | 600 + datanode_2 | 3 | 300 + datanode_2 | 4 | 400 +(6 rows) + +select xl_nodename_from_id1(xc_node_id), * from xl_ddl_tbl2; + xl_nodename_from_id1 | a | b +----------------------+---+----- + datanode_1 | 1 | 1 + datanode_1 | 2 | 2 + datanode_1 | 5 | 500 + datanode_1 | 6 | 600 + datanode_2 | 3 | 3 + datanode_2 | 4 | 4 +(6 rows) + +update xl_found_test_tbl set b = a;--re-set drop table xl_Pline1; drop function xl_nodename_from_id1(integer); drop function xl_insert_Pline_test(int); drop function xl_update_Pline_test(int); drop function xl_delete_Pline_test(int); +drop function xl_test_table_func_row(); +drop function xl_test_table_func_rec(); +drop function xl_test_found(); +drop function xl_read_from_one_node(name); +drop function xl_write_to_one_node(name); +drop function xl_write_to_multiple_nodes(); +drop function xl_write_read_from_one_node(name); +drop function xl_join_using_1node(name); +drop function xl_join_using_more_nodes(name); +drop function xl_ddl_commands(name); +drop TABLE xl_found_test_tbl; +drop TABLE xl_ddl_tbl2; +drop TABLE xl_join1_tbl; +drop TABLE xl_join2_tbl; +ERROR: table "xl_join2_tbl" does not exist +drop TABLE xl_join4_tbl; diff --git a/src/test/regress/sql/xl_user_defined_functions.sql b/src/test/regress/sql/xl_user_defined_functions.sql index 2aeace6fb4..d1790947ae 100755 --- a/src/test/regress/sql/xl_user_defined_functions.sql +++ b/src/test/regress/sql/xl_user_defined_functions.sql @@ -113,9 +113,278 @@ select xl_delete_Pline_test(1); select xl_nodename_from_id1(xc_node_id), * from xl_Pline1 order by slotname; +-- other user-defined-functions: +-- +-- Test the FOUND magic variable +-- +-- table distributed by hash on a by default +CREATE TABLE xl_found_test_tbl (a int, b int) ; + +create function xl_test_found() + returns boolean as ' + declare + begin + insert into xl_found_test_tbl values (1, 1); + if FOUND then + insert into xl_found_test_tbl values (2, 2); + end if; + + update xl_found_test_tbl set b = 100 where a = 1; + if FOUND then + insert into xl_found_test_tbl values (3, 3); + end if; + + delete from xl_found_test_tbl where a = 9999; -- matches no rows + if not FOUND then + insert into xl_found_test_tbl values (4, 4); + end if; + + for i in 1 .. 10 loop + -- no need to do anything + end loop; + if FOUND then + insert into xl_found_test_tbl values (5, 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 xl_found_test_tbl values (6, 6); + end if; + return true; + end;' language plpgsql; + +select xl_test_found(); +select * from xl_found_test_tbl order by 1; + +-- +-- Test set-returning functions for PL/pgSQL +-- + +create function xl_test_table_func_rec() returns setof xl_found_test_tbl as ' +DECLARE + rec RECORD; +BEGIN + FOR rec IN select * from xl_found_test_tbl LOOP + RETURN NEXT rec; + END LOOP; + RETURN; +END;' language plpgsql; + +select * from xl_test_table_func_rec() order by 1; + + +-- reads from just 1 node, reads from multiple nodes, writes to just node, writes to multiple nodes, writes to one node and then read again, do a join which requires data from one node, multiple nodes, use ddl etc + +--reads from multiple nodes +create function xl_test_table_func_row() returns setof xl_found_test_tbl as ' +DECLARE + row xl_found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from xl_found_test_tbl LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; + +select * from xl_test_table_func_row() order by 1; + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + + +--reads from just 1 node +create function xl_read_from_one_node(name) returns setof xl_found_test_tbl as ' +DECLARE + row xl_found_test_tbl%ROWTYPE; +BEGIN + FOR row IN select * from xl_found_test_tbl where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; + +select xl_read_from_one_node('datanode_1'); +select xl_read_from_one_node('datanode_2'); + +update xl_found_test_tbl set b = a;--re-set + +--writes to just node +create function xl_write_to_one_node(name) returns void as ' +BEGIN + update xl_found_test_tbl set b = b * 100 where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1); + RETURN; +END;' language plpgsql; + +select xl_write_to_one_node('datanode_1'); + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + +update xl_found_test_tbl set b = a;--re-set + +--writes to multiple nodes +create function xl_write_to_multiple_nodes() returns void as ' +BEGIN + update xl_found_test_tbl set b = b * 100; + RETURN; +END;' language plpgsql; + +select xl_write_to_multiple_nodes(); + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + +update xl_found_test_tbl set b = a;--re-set + +--writes to one node and then read again, + +create function xl_write_read_from_one_node(name) returns setof xl_found_test_tbl as ' +DECLARE + row xl_found_test_tbl%ROWTYPE; +BEGIN + update xl_found_test_tbl set b = b * 100 where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1); + FOR row IN select * from xl_found_test_tbl where a in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) LOOP + RETURN NEXT row; + END LOOP; + RETURN; +END;' language plpgsql; + +select xl_write_read_from_one_node('datanode_1'); + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + +update xl_found_test_tbl set b = a;--re-set + +-- do a join which requires data from one node, +-- table is replicated on both nodes. +CREATE TABLE xl_join1_tbl (c int, d int) distribute by replication; + +insert into xl_join1_tbl values (1, 100); +insert into xl_join1_tbl values (2, 200); +insert into xl_join1_tbl values (3, 300); +insert into xl_join1_tbl values (4, 400); +insert into xl_join1_tbl values (5, 500); +insert into xl_join1_tbl values (6, 600); + +create function xl_join_using_1node(name) returns void as ' +BEGIN + update xl_found_test_tbl set b = xl_join1_tbl.d from xl_join1_tbl + where xl_join1_tbl.c in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) + and xl_found_test_tbl.a = xl_join1_tbl.c + and xl_join1_tbl.c <= 3; + RETURN; +END;' language plpgsql; + +select xl_join_using_1node('datanode_1'); + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + +update xl_found_test_tbl set b = a;--re-set + +-- do a join which requires data from multiple nodes +-- table distributed by hash(c) by default +CREATE TABLE xl_join2_tbl (c int, d int); + +insert into xl_join2_tbl values (1, 100); +insert into xl_join2_tbl values (2, 200); +insert into xl_join2_tbl values (3, 300); +insert into xl_join2_tbl values (4, 400); +insert into xl_join2_tbl values (5, 500); +insert into xl_join2_tbl values (6, 600); + + +create function xl_join_using_more_nodes(name) returns void as ' +BEGIN + update xl_found_test_tbl set b = xl_join2_tbl.d from xl_join2_tbl + where xl_join2_tbl.c in (select a from xl_found_test_tbl where xl_nodename_from_id1(xc_node_id) = $1) + and xl_found_test_tbl.a = xl_join2_tbl.c + and xl_join2_tbl.c >= 3; + RETURN; +END;' language plpgsql; + +select xl_join_using_more_nodes('datanode_1'); + +select xl_nodename_from_id1(xc_node_id), * from xl_found_test_tbl; + +update xl_found_test_tbl set b = a;--re-set + +-- use ddl etc +--DDL Commands - Create - Drop - Alter - Rename - Truncate + +-- +create function xl_ddl_commands(name) returns void as ' +BEGIN + /* table distributed by hash on a by default*/ + CREATE TABLE xl_ddl_tbl1 (a int, b int) ; + insert into xl_ddl_tbl1 values (1,1); + insert into xl_ddl_tbl1 values (2,2); + insert into xl_ddl_tbl1 values (3,3); + insert into xl_ddl_tbl1 values (4,4); + insert into xl_ddl_tbl1 values (5,5); + insert into xl_ddl_tbl1 values (6,6); + + drop table xl_join2_tbl; + + /*table distributed by hash(c) by default*/ + CREATE TABLE xl_join3_tbl (c int); + + insert into xl_join3_tbl values (1); + insert into xl_join3_tbl values (2); + insert into xl_join3_tbl values (3); + insert into xl_join3_tbl values (4); + insert into xl_join3_tbl values (5); + insert into xl_join3_tbl values (6); + + alter table xl_join3_tbl add column d int; + + update xl_join3_tbl set d = c * 100; + + alter table xl_ddl_tbl1 rename to xl_ddl_tbl2; + + alter table xl_join3_tbl rename to xl_join4_tbl; + + truncate table xl_join1_tbl; + + update xl_ddl_tbl2 set b = xl_join4_tbl.d from xl_join4_tbl + where xl_join4_tbl.c in (select a from xl_ddl_tbl2 where xl_nodename_from_id1(xc_node_id) = $1) + and xl_ddl_tbl2.a = xl_join4_tbl.c + and xl_join4_tbl.c >= 3; + + RETURN; +END;' language plpgsql; + +select xl_ddl_commands('datanode_1'); + +select xl_nodename_from_id1(xc_node_id), * from xl_join1_tbl; --truncated + +select xl_nodename_from_id1(xc_node_id), * from xl_join2_tbl; -- dropped + +select xl_nodename_from_id1(xc_node_id), * from xl_join3_tbl; -- renamed to xl_join4_tbl + +select xl_nodename_from_id1(xc_node_id), * from xl_join4_tbl; + +select xl_nodename_from_id1(xc_node_id), * from xl_ddl_tbl2; + +update xl_found_test_tbl set b = a;--re-set + drop table xl_Pline1; drop function xl_nodename_from_id1(integer); - drop function xl_insert_Pline_test(int); drop function xl_update_Pline_test(int); drop function xl_delete_Pline_test(int); +drop function xl_test_table_func_row(); +drop function xl_test_table_func_rec(); +drop function xl_test_found(); +drop function xl_read_from_one_node(name); +drop function xl_write_to_one_node(name); +drop function xl_write_to_multiple_nodes(); +drop function xl_write_read_from_one_node(name); +drop function xl_join_using_1node(name); +drop function xl_join_using_more_nodes(name); +drop function xl_ddl_commands(name); +drop TABLE xl_found_test_tbl; +drop TABLE xl_ddl_tbl2; +drop TABLE xl_join1_tbl; +drop TABLE xl_join2_tbl; +drop TABLE xl_join4_tbl; + |