From 125314d7f8392eb0978ded488ef26200f060079e Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Fri, 19 Jan 2024 16:08:52 +0100
Subject: [PATCH 08/18] plpgsql tests

---
 src/pl/plpgsql/src/Makefile                   |   3 +-
 .../src/expected/plpgsql_session_variable.out | 382 ++++++++++++++++++
 src/pl/plpgsql/src/meson.build                |   1 +
 .../src/sql/plpgsql_session_variable.sql      | 289 +++++++++++++
 4 files changed, 674 insertions(+), 1 deletion(-)
 create mode 100644 src/pl/plpgsql/src/expected/plpgsql_session_variable.out
 create mode 100644 src/pl/plpgsql/src/sql/plpgsql_session_variable.sql

diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
index 63cb96fae3..bbcae27d42 100644
--- a/src/pl/plpgsql/src/Makefile
+++ b/src/pl/plpgsql/src/Makefile
@@ -35,7 +35,8 @@ REGRESS_OPTS = --dbname=$(PL_TESTDB)
 REGRESS = plpgsql_array plpgsql_cache plpgsql_call plpgsql_control \
 	plpgsql_copy plpgsql_domain plpgsql_misc \
 	plpgsql_record plpgsql_simple plpgsql_transaction \
-	plpgsql_trap plpgsql_trigger plpgsql_varprops
+	plpgsql_trap plpgsql_trigger plpgsql_varprops \
+	plpgsql_session_variable
 
 # where to find gen_keywordlist.pl and subsidiary files
 TOOLSDIR = $(top_srcdir)/src/tools
diff --git a/src/pl/plpgsql/src/expected/plpgsql_session_variable.out b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
new file mode 100644
index 0000000000..6e8b4134c0
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_session_variable.out
@@ -0,0 +1,382 @@
+-- test of session variables
+CREATE VARIABLE plpgsql_sv_var AS numeric;
+LET plpgsql_sv_var = pi();
+-- passing parameters to DO block
+DO $$
+BEGIN
+  RAISE NOTICE 'value of session variable is %', plpgsql_sv_var;
+END;
+$$;
+NOTICE:  value of session variable is 3.14159265358979
+-- passing output from DO block;
+DO $$
+BEGIN
+  LET plpgsql_sv_var = 2 * pi();
+END
+$$;
+SELECT plpgsql_sv_var AS "pi_multiply_2";
+  pi_multiply_2   
+------------------
+ 6.28318530717959
+(1 row)
+
+DROP VARIABLE plpgsql_sv_var;
+-- test access from PL/pgSQL
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+CREATE OR REPLACE FUNCTION writer_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = 10;
+  LET plpgsql_sv_var2 = pi();
+  -- very long value
+  LET plpgsql_sv_var3 = format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION updater_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = plpgsql_sv_var1 + 100;
+  LET plpgsql_sv_var2 = plpgsql_sv_var2 + 100000000000;
+  -- very long value
+  LET plpgsql_sv_var3 = plpgsql_sv_var3 || format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION reader_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE 'var1 = %', plpgsql_sv_var1;
+  RAISE NOTICE 'var2 = %', plpgsql_sv_var2;
+  RAISE NOTICE 'length of var3 = %', length(plpgsql_sv_var3);
+END;
+$$ LANGUAGE plpgsql;
+-- execute under transaction
+BEGIN;
+SELECT writer_func();
+ writer_func 
+-------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+ reader_func 
+-------------
+ 
+(1 row)
+
+SELECT updater_func();
+ updater_func 
+--------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+ reader_func 
+-------------
+ 
+(1 row)
+
+END;
+-- execute out of transaction
+SELECT writer_func();
+ writer_func 
+-------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+ reader_func 
+-------------
+ 
+(1 row)
+
+SELECT updater_func();
+ updater_func 
+--------------
+ 
+(1 row)
+
+SELECT reader_func();
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+ reader_func 
+-------------
+ 
+(1 row)
+
+-- execute inside PL/pgSQL block
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+-- plan caches should be correctly invalidated
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+-- should to work again
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+NOTICE:  var1 = 10
+NOTICE:  var2 = 3.14159265358979
+NOTICE:  length of var3 = 10002
+NOTICE:  var1 = 110
+NOTICE:  var2 = 100000000003.14159265358979
+NOTICE:  length of var3 = 20004
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+DROP FUNCTION writer_func;
+DROP FUNCTION reader_func;
+DROP FUNCTION updater_func;
+-- another check of correct plan cache invalidation
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+CREATE OR REPLACE FUNCTION test_func()
+RETURNS void AS $$
+DECLARE v int[] DEFAULT '{}';
+BEGIN
+  LET plpgsql_sv_var1 = 1;
+  v[plpgsql_sv_var1] = 100;
+  RAISE NOTICE '%', v;
+  LET plpgsql_sv_var2 = v;
+  LET plpgsql_sv_var2[plpgsql_sv_var1] = -1;
+  RAISE NOTICE '%', plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+SELECT test_func();
+NOTICE:  {100}
+NOTICE:  {-1}
+ test_func 
+-----------
+ 
+(1 row)
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+SELECT test_func();
+NOTICE:  {100}
+NOTICE:  {-1}
+ test_func 
+-----------
+ 
+(1 row)
+
+DROP FUNCTION test_func();
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+-- check secure access
+CREATE ROLE regress_var_owner_role;
+CREATE ROLE regress_var_reader_role;
+CREATE ROLE regress_var_exec_role;
+GRANT ALL ON SCHEMA public TO regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+SET ROLE TO regress_var_owner_role;
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+LET plpgsql_sv_var1 = 10;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_reader_role;
+CREATE OR REPLACE FUNCTION var_read_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_exec_role;
+-- should to fail
+SELECT var_read_func();
+ERROR:  permission denied for session variable plpgsql_sv_var1
+CONTEXT:  SQL expression "plpgsql_sv_var1"
+PL/pgSQL function var_read_func() line 3 at RAISE
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_owner_role;
+GRANT SELECT ON VARIABLE plpgsql_sv_var1 TO regress_var_reader_role;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_exec_role;
+-- should be ok
+SELECT var_read_func();
+NOTICE:  10
+ var_read_func 
+---------------
+ 
+(1 row)
+
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_owner_role;
+DROP VARIABLE plpgsql_sv_var1;
+SET ROLE TO DEFAULT;
+SET ROLE TO regress_var_exec_role;
+-- should to fail, but not crash
+SELECT var_read_func();
+ERROR:  column "plpgsql_sv_var1" does not exist
+LINE 1: plpgsql_sv_var1
+        ^
+QUERY:  plpgsql_sv_var1
+CONTEXT:  PL/pgSQL function var_read_func() line 3 at RAISE
+SET ROLE TO DEFAULT;
+DROP FUNCTION var_read_func;
+REVOKE ALL ON SCHEMA public FROM regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+DROP ROLE regress_var_owner_role;
+DROP ROLE regress_var_reader_role;
+DROP ROLE regress_var_exec_role;
+-- returns updated value
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE OR REPLACE FUNCTION inc_var_int(int)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var1 = COALESCE(plpgsql_sv_var1 + $1, $1);
+  RETURN plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc_var_int(1);
+ inc_var_int 
+-------------
+           1
+(1 row)
+
+SELECT inc_var_int(1);
+ inc_var_int 
+-------------
+           2
+(1 row)
+
+SELECT inc_var_int(1);
+ inc_var_int 
+-------------
+           3
+(1 row)
+
+SELECT inc_var_int(1) FROM generate_series(1,10);
+ inc_var_int 
+-------------
+           4
+           5
+           6
+           7
+           8
+           9
+          10
+          11
+          12
+          13
+(10 rows)
+
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+LET plpgsql_sv_var2 = 0.0;
+CREATE OR REPLACE FUNCTION inc_var_num(numeric)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var2 = COALESCE(plpgsql_sv_var2 + $1, $1);
+  RETURN plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc_var_num(1.0);
+ inc_var_num 
+-------------
+           1
+(1 row)
+
+SELECT inc_var_num(1.0);
+ inc_var_num 
+-------------
+           2
+(1 row)
+
+SELECT inc_var_num(1.0);
+ inc_var_num 
+-------------
+           3
+(1 row)
+
+SELECT inc_var_num(1.0) FROM generate_series(1,10);
+ inc_var_num 
+-------------
+           4
+           5
+           6
+           7
+           8
+           9
+          10
+          11
+          12
+          13
+(10 rows)
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+DROP FUNCTION inc_var_int;
+DROP FUNCTION inc_var_num;
+-- plpgsql variables are preferred against session variables
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+DO $$
+<<myblock>>
+DECLARE plpgsql_sv_var1 int;
+BEGIN
+  LET plpgsql_sv_var1 = 100;
+
+  plpgsql_sv_var1 := 1000;
+
+  -- print 100;
+  RAISE NOTICE 'session variable is %', public.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'plpgsql variable is %', myblock.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'variable is %', plpgsql_sv_var1;
+END;
+$$;
+NOTICE:  session variable is 100
+NOTICE:  plpgsql variable is 1000
+NOTICE:  variable is 1000
+DROP VARIABLE plpgsql_sv_var1;
+-- the value should not be corrupted
+CREATE VARIABLE plpgsql_sv_v text;
+LET plpgsql_sv_v = 'abc';
+CREATE FUNCTION ffunc()
+RETURNS text AS $$
+BEGIN
+  RETURN gfunc(plpgsql_sv_v);
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION gfunc(t text)
+RETURNS text AS $$
+BEGIN
+  LET plpgsql_sv_v = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+select ffunc();
+ ffunc 
+-------
+ abc
+(1 row)
+
+DROP FUNCTION ffunc();
+DROP FUNCTION gfunc(text);
+DROP VARIABLE plpgsql_sv_v;
diff --git a/src/pl/plpgsql/src/meson.build b/src/pl/plpgsql/src/meson.build
index 3dd734b776..3905c0b6d4 100644
--- a/src/pl/plpgsql/src/meson.build
+++ b/src/pl/plpgsql/src/meson.build
@@ -88,6 +88,7 @@ tests += {
       'plpgsql_trap',
       'plpgsql_trigger',
       'plpgsql_varprops',
+      'plpgsql_session_variable',
     ],
   },
 }
diff --git a/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
new file mode 100644
index 0000000000..398e7fe2a7
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_session_variable.sql
@@ -0,0 +1,289 @@
+-- test of session variables
+CREATE VARIABLE plpgsql_sv_var AS numeric;
+
+LET plpgsql_sv_var = pi();
+
+-- passing parameters to DO block
+DO $$
+BEGIN
+  RAISE NOTICE 'value of session variable is %', plpgsql_sv_var;
+END;
+$$;
+
+-- passing output from DO block;
+DO $$
+BEGIN
+  LET plpgsql_sv_var = 2 * pi();
+END
+$$;
+
+SELECT plpgsql_sv_var AS "pi_multiply_2";
+
+DROP VARIABLE plpgsql_sv_var;
+
+-- test access from PL/pgSQL
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+
+CREATE OR REPLACE FUNCTION writer_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = 10;
+  LET plpgsql_sv_var2 = pi();
+  -- very long value
+  LET plpgsql_sv_var3 = format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION updater_func()
+RETURNS void AS $$
+BEGIN
+  LET plpgsql_sv_var1 = plpgsql_sv_var1 + 100;
+  LET plpgsql_sv_var2 = plpgsql_sv_var2 + 100000000000;
+  -- very long value
+  LET plpgsql_sv_var3 = plpgsql_sv_var3 || format('(%s)', repeat('*', 10000));
+END;
+$$ LANGUAGE plpgsql;
+
+
+CREATE OR REPLACE FUNCTION reader_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE 'var1 = %', plpgsql_sv_var1;
+  RAISE NOTICE 'var2 = %', plpgsql_sv_var2;
+  RAISE NOTICE 'length of var3 = %', length(plpgsql_sv_var3);
+END;
+$$ LANGUAGE plpgsql;
+
+-- execute under transaction
+BEGIN;
+SELECT writer_func();
+SELECT reader_func();
+SELECT updater_func();
+SELECT reader_func();
+END;
+
+-- execute out of transaction
+SELECT writer_func();
+SELECT reader_func();
+SELECT updater_func();
+SELECT reader_func();
+
+-- execute inside PL/pgSQL block
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+
+-- plan caches should be correctly invalidated
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+CREATE VARIABLE plpgsql_sv_var3 AS varchar;
+
+-- should to work again
+DO $$
+BEGIN
+  PERFORM writer_func();
+  PERFORM reader_func();
+  PERFORM updater_func();
+  PERFORM reader_func();
+END;
+$$;
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2, plpgsql_sv_var3;
+
+DROP FUNCTION writer_func;
+DROP FUNCTION reader_func;
+DROP FUNCTION updater_func;
+
+-- another check of correct plan cache invalidation
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+
+CREATE OR REPLACE FUNCTION test_func()
+RETURNS void AS $$
+DECLARE v int[] DEFAULT '{}';
+BEGIN
+  LET plpgsql_sv_var1 = 1;
+  v[plpgsql_sv_var1] = 100;
+  RAISE NOTICE '%', v;
+  LET plpgsql_sv_var2 = v;
+  LET plpgsql_sv_var2[plpgsql_sv_var1] = -1;
+  RAISE NOTICE '%', plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT test_func();
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+CREATE VARIABLE plpgsql_sv_var2 AS int[];
+
+SELECT test_func();
+
+DROP FUNCTION test_func();
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+
+-- check secure access
+CREATE ROLE regress_var_owner_role;
+CREATE ROLE regress_var_reader_role;
+CREATE ROLE regress_var_exec_role;
+
+GRANT ALL ON SCHEMA public TO regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+
+SET ROLE TO regress_var_owner_role;
+
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+LET plpgsql_sv_var1 = 10;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_reader_role;
+
+CREATE OR REPLACE FUNCTION var_read_func()
+RETURNS void AS $$
+BEGIN
+  RAISE NOTICE '%', plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_exec_role;
+
+-- should to fail
+SELECT var_read_func();
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_owner_role;
+GRANT SELECT ON VARIABLE plpgsql_sv_var1 TO regress_var_reader_role;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_exec_role;
+
+-- should be ok
+SELECT var_read_func();
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_owner_role;
+
+DROP VARIABLE plpgsql_sv_var1;
+
+SET ROLE TO DEFAULT;
+
+SET ROLE TO regress_var_exec_role;
+
+-- should to fail, but not crash
+SELECT var_read_func();
+
+SET ROLE TO DEFAULT;
+
+DROP FUNCTION var_read_func;
+
+REVOKE ALL ON SCHEMA public FROM regress_var_owner_role, regress_var_reader_role, regress_var_exec_role;
+
+DROP ROLE regress_var_owner_role;
+DROP ROLE regress_var_reader_role;
+DROP ROLE regress_var_exec_role;
+
+-- returns updated value
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+
+CREATE OR REPLACE FUNCTION inc_var_int(int)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var1 = COALESCE(plpgsql_sv_var1 + $1, $1);
+  RETURN plpgsql_sv_var1;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc_var_int(1);
+SELECT inc_var_int(1);
+SELECT inc_var_int(1);
+
+SELECT inc_var_int(1) FROM generate_series(1,10);
+
+CREATE VARIABLE plpgsql_sv_var2 AS numeric;
+
+LET plpgsql_sv_var2 = 0.0;
+
+CREATE OR REPLACE FUNCTION inc_var_num(numeric)
+RETURNS int AS $$
+BEGIN
+  LET plpgsql_sv_var2 = COALESCE(plpgsql_sv_var2 + $1, $1);
+  RETURN plpgsql_sv_var2;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc_var_num(1.0);
+SELECT inc_var_num(1.0);
+SELECT inc_var_num(1.0);
+
+SELECT inc_var_num(1.0) FROM generate_series(1,10);
+
+DROP VARIABLE plpgsql_sv_var1, plpgsql_sv_var2;
+
+DROP FUNCTION inc_var_int;
+DROP FUNCTION inc_var_num;
+
+-- plpgsql variables are preferred against session variables
+CREATE VARIABLE plpgsql_sv_var1 AS int;
+
+DO $$
+<<myblock>>
+DECLARE plpgsql_sv_var1 int;
+BEGIN
+  LET plpgsql_sv_var1 = 100;
+
+  plpgsql_sv_var1 := 1000;
+
+  -- print 100;
+  RAISE NOTICE 'session variable is %', public.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'plpgsql variable is %', myblock.plpgsql_sv_var1;
+
+  -- print 1000
+  RAISE NOTICE 'variable is %', plpgsql_sv_var1;
+END;
+$$;
+
+DROP VARIABLE plpgsql_sv_var1;
+
+-- the value should not be corrupted
+CREATE VARIABLE plpgsql_sv_v text;
+LET plpgsql_sv_v = 'abc';
+
+CREATE FUNCTION ffunc()
+RETURNS text AS $$
+BEGIN
+  RETURN gfunc(plpgsql_sv_v);
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION gfunc(t text)
+RETURNS text AS $$
+BEGIN
+  LET plpgsql_sv_v = 'BOOM!';
+  RETURN t;
+END;
+$$ LANGUAGE plpgsql;
+
+select ffunc();
+
+DROP FUNCTION ffunc();
+DROP FUNCTION gfunc(text);
+
+DROP VARIABLE plpgsql_sv_v;
-- 
2.43.0

